This tutorial is part of a Collection: 01. DirectX 9 - Braynzar Soft Tutorials
4120
views
03. Initializing Direct3D
In this Lesson you will learn how to Initialize a Direct3D Object. Initializing Direct3D assumes you have knowledge of how to program with windows API and can create a blank window.
We have one more thing to do to start displaying our crazy minds using DirectX. And that is to Initialize Direct3D. By the end of this lesson, you should have a nice little window with a blue background. Ok, lets get on this thing. To get the Functions and structures and all that other stuff necessary to compile a Direct3D Program, you need to include the DirectX SDK library and header directories. Follow these steps: 1. After you open your project (Remember this lesson builds off the Creating a Win32 Window Code), In the Tools menu at the top, go down to options... 2. Expand the'Projects and Solutions' in the menu on the left. 3.Then click 'VC++ Directories' 4.On the right side at the top where it says 'Show directories for:', pull down that menu and select 'Include Files' 5.Click on the little folder button right below that, or press Ctrl+Insert 6.It should create a new line. Click on the button at the right of the line that has the three periods(...). 7.A window will pop-up. Find 'Microsoft DirectX SDK (version)', then the includes directory. Mine was at C:\Program Files\Microsoft DirectX SDK (March 2008)\Include. 8.After you have your DirectX includes directory, go to the top again where it says 'Show directories for:'. 9.Drop down the menu and select 'Library Files'. 10.Click on the folder icon again, or press Ctrl+Insert 11.Click on the Button with the three periods on the right side of the line again. 12.Find the 'Microsoft DirectX SDK (version)' again, but this time instead of the includes directory, find the 'lib' folder then the 'x86' folder. Mine was C:\Program Files\Microsoft DirectX SDK (March 2008)\Lib\x86. That should be all the setting up you need to do to compile this DirectX app. The first two lines of code are including the direct3d header file and library file. These are necessary to include so we can use the functions, classes, structures and all the other things directx offers. You don't need to include the second line if you already included the library file in your project settings. #include <d3d9.h> #pragma comment (lib, "d3d9.lib") The next new line of code is a long pointer to the Direct3D interface. IDirect3D9 is used to create Direct3D objects and set up your environment by enumerating and retrieving capabilities of your Direct3D device (Which is the device that will do the vertex and other graphical processing). By making a pointer, we will be accessing the Direct3D interface members indirectly. IDirect3D9* d3d9 = NULL; After that we make another pointer to IDirect3DDevice9 called d3dDevice. We make sure the structure is blank by making it equal to NULL. The IDirect3DDevice9 class represents the physical device which your application will use to process and create 3D graphics. IDirect3DDevice9* d3dDevice = NULL; These are the functions that will set up, display, then clean up our Direct3D scene. We'll talk more about these soon. bool SetupScene(); void CleanUpScene(); bool RenderScene(); Next we define our InitializeD3DWindow() function. This is the same function as InitializeWindow() in the Create a Win32 Window lesson. I changed the name 'cause this is where we will be adding the code to initialize Direct3D. There are two new parameters now, D3DDEVTYPE deviceType and IDirect3DDevice9** d3dDevice. The first one is the type of device that will be doing the vertex processing and shading. Pretty much it's iether hardware or software. The second one is a pointer to the IDirect3DDevice9 object we defined. We will set this to d3dDevice when the time comes to set it. bool InitializeD3DWindow(HINSTANCE hInstance, int ShowWnd, int width, int height, bool windowed, D3DDEVTYPE deviceType, IDirect3DDevice9** d3dDevice); Next is the SetupScene() function. We will not have anything in our scene yet because we are only initializing Direct3D right now. bool SetupScene(){ return true; } CleanUpScene() will be used to free all objects created in the SetupScene() function. We have nothing yet in the SetupScene() function so we have nothing to clean up. void CleanUpScene(){ //Release memory return; } RenderScene() Will be used to display one frame at a time. The parameter float timeDelta is used to sync our animations with the frame rate. bool RenderScene(float timeDelta) { This next line is saying "If we still have a device, then...". if( d3dDevice ) { This line will clear the screen every frame before we start drawing again. The structure looks like this: HRESULT Clear( DWORD Count, CONST D3DRECT * pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil ); Count - Number of rectangles in the array at pRects. Must be set to 0 if pRects is NULL. May not be 0 if pRects is a valid pointer. pRects - Pointer to an array of D3DRECT structures that describe the rectangles to clear. Set a rectangle to the dimensions of the rendering target to clear the entire surface. Each rectangle uses screen coordinates that correspond to points on the render target. Coordinates are clipped to the bounds of the viewport rectangle. To indicate that the entire viewport rectangle is to be cleared, set this parameter to NULL and Count to 0. Flags - Combination of one or more D3DCLEAR flags that specify the surface(s) that will be cleared. Color - Clear a render target to this ARGB color. Z - Clear the depth buffer to this new z value which ranges from 0 to 1. Stencil - Clear the stencil buffer to this new value which ranges from 0 to 2n - 1 (n is the bit depth of the stencil buffer). d3dDevice->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000055, 1.0f, 0); Theres not much to these next two lines. Whatever we need drawn to the screen will go between these two lines. d3dDevice->BeginScene(); d3dDevice->EndScene(); Now we present the next buffer by using this next command. Present takes four parameters. This is it's structure: HRESULT Present( CONST RECT * pSourceRect, CONST RECT * pDestRect, HWND hDestWindowOverride, CONST RGNDATA * pDirtyRegion ); pSourceRect - Pointer to a value that must be NULL unless the swap chain was created with D3DSWAPEFFECT_COPY. pSourceRect is a pointer to a RECT structure containing the source rectangle. If NULL, the entire source surface is presented. If the rectangle exceeds the source surface, the rectangle is clipped to the source surface. pDestRect - Pointer to a value that must be NULL unless the swap chain was created with D3DSWAPEFFECT_COPY. pDestRect is a pointer to a RECT structure containing the destination rectangle, in window client coordinates. If NULL, the entire client area is filled. If the rectangle exceeds the destination client area, the rectangle is clipped to the destination client area. hDestWindowOverride - Pointer to a destination window whose client area is taken as the target for this presentation. If this value is NULL, then the hWndDeviceWindow member of D3DPRESENT_PARAMETERS is taken. pDirtyRegion - Value must be NULL unless the swap chain was created with D3DSWAPEFFECT_COPY. For more information about swap chains, see Flipping Surfaces (Direct3D 9) and D3DSWAPEFFECT. If this value is non-NULL, the contained region is expressed in back buffer coordinates. The rectangles within the region are the minimal set of pixels that need to be updated. This method takes these rectangles into account when optimizing the presentation by copying only the pixels within the region, or some suitably expanded set of rectangles. This is an aid to optimization only, and the application should not rely on the region being copied exactly. The implementation can choose to copy the whole source rectangle. d3dDevice->Present(0, 0, 0, 0); //Display our newly created scene } return true; } This will initialize our d3d window, and if it fails, will show a message. You can see we used D3DDEVTYPE_HAL for the device type. This says we are going to use hardware for our vertex processing and software, hardware or a mix of both for the shading and lighting. if(!InitializeD3DWindow(hInstance, nShowCmd, Width, Height, true, D3DDEVTYPE_HAL, &d3dDevice)) { MessageBox(0, "Window/D3D Initialization - Failed", "Error", MB_OK); return 0; } We call our SetupScene() function, and if that fails, return an error. if(!SetupScene()) { MessageBox(0, "SetupScene() - FAILED", 0, 0); return 0; } Now we jump into the fun, the message loop. This is where our windows messages will be processed. It takes a pointer to the function that has the display data. It needs to know the function we use to display our scene so it can draw it during idle processing, which is no windows messages. messageloop(RenderScene); After we get done with the heart of our program, we need to cleanup all the memory we allocated in our SetupScene() function. CleanUpScene(); To end this function, we release our IDirect3DDevice9 object by calling IDirect3DDevice9->Release(), then return 0 for no errors. d3dDevice->Release(); return 0; } Now we're into the InitializeD3DWindow() function. I've already explained the first part in the Win32 lessons, which initializes the window, so i'll just go to the part where we start initializing Direct3D. bool InitializeD3DWindow(HINSTANCE hInstance, int ShowWnd, int width, int height, bool windowed, D3DDEVTYPE deviceType, IDirect3DDevice9** d3dDevice) { Right after the line UpdateWindow(hwnd); is where we start initializing Direct3D. The first line after UpdateWindow(hwnd); is where we initialize the Direct3D interface. D3D_SDK_VERSION is passed to this function to ensure that the header files against which an application is compiled match the version of the runtime DLL's that are installed on the machine. d3d9 = Direct3DCreate9( D3D_SDK_VERSION); Check to make sure our d3d9 object was created. if(!d3d9){ return false; } All Direct3D features have a corresponding data member or bit in the D3DCAPS9 structure. First we make a new instance of D3DCAPS9, then we get the device capabilities using the GetDeviceCaps() function. Once we get the device capabilities, we can check the hardware that is being used to see if it is capable of processing a certain feature such as vertex processing or lighting calculations. D3DCAPS9 caps; d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, deviceType, &caps); Now we check to see if the hardware supports vertex processing. If it does not, we will need to do all the vertex processing using software. So if it supports it, we set vertexproc to D3DCREATE_HARDWARE_VERTEXPROCESSING, if it does not, we set it to D3DCREATE_SOFTWARE_VERTEXPROCESSING. int vertexproc = NULL; if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) vertexproc = D3DCREATE_HARDWARE_VERTEXPROCESSING; else vertexproc = D3DCREATE_SOFTWARE_VERTEXPROCESSING; This is where we initialize a new instance of the D3DPRESENT_PARAMETERS structure then fill it out. D3DPRESENT_PARAMETERS holds a variety of data members that we will set which will define characteristics of the IDirect3DDevice9 interface we will create. D3DPRESENT_PARAMETERS d3dpp; This is the D3DPRESENT_PARAMETERS structure: typedef struct _D3DPRESENT_PARAMETERS_ { UINT BackBufferWidth; UINT BackBufferHeight; D3DFORMAT BackBufferFormat; UINT BackBufferCount; D3DMULTISAMPLE_TYPE MultiSampleType; DWORD MultiSampleQuality; D3DSWAPEFFECT SwapEffect; HWND hDeviceWindow; BOOL Windowed; BOOL EnableAutoDepthStencil; D3DFORMAT AutoDepthStencilFormat; DWORD Flags; UINT FullScreen_RefreshRateInHz; UINT PresentationInterval; } D3DPRESENT_PARAMETERS; BackBufferWidth - Width of backbuffer in pixels BackBufferHeight - Height of backbuffer in pixels ackBufferFormat - Pixel format of the back buffer (e.g., 32-bit pixel format: D3DFMT_A8R8G8B8) BackBufferCount - Number of backbuffers MultiSampleType - Type of multi-sampling to do with backbuffer MultiSampleQuality - The quality of multi-sampling to do with backbuffer SwapEffect - Specifies how the backbuffers will be swapped hDeviceWindow - The handle to the application window that you want to draw on Windowed - Fullscreen == false. Windowed == true. EnableAutoDepthStencil - Set to true to have Direct3D create and maintain the depth/stencil buffer automatically AutoDepthStencilFormat - The format of the depth/stencil buffer (e.g., 24-bit depth with 8 bits reserved for the stencil buffer: D3DFMT_D24S8). Flags - Extra characteristics. Set to NULL for no extra characteristics or set to a member of D3DPRESENTFLAG for an extra characteristic or two. FullScreen_RefreshRateInHz - Refresh rate. D3DPRESENT_RATE_DEFAULT will use the default refresh rate. PresentationInterval - Member of the D3DPRESENT set d3dpp.BackBufferWidth = width; d3dpp.BackBufferHeight = height; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; d3dpp.BackBufferCount = 1; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.MultiSampleQuality = NULL; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.hDeviceWindow = hwnd; d3dpp.Windowed = windowed; d3dpp.EnableAutoDepthStencil = true; d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; d3dpp.Flags = NULL; d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; This is where we create the device. First we make an instance of HRESULT so we can make sure the device was created. Then we create the device with the CreateDevice member of the IDirect3D9 interface. the CreateDevice() structure looks like this: HRESULT CreateDevice( UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS * pPresentationParameters, IDirect3DDevice9 ** ppReturnedDeviceInterface ); Adapter - Ordinal number that denotes the display adapter. D3DADAPTER_DEFAULT is always the primary display adapter. DeviceType - Member of the D3DDEVTYPE enumerated type that denotes the desired device type. If the desired device type is not available, the method will fail. hFocusWindow - Handle to window. BehaviorFlags - Combination of one or more options that control device creation. Check out D3DCREATE. pPresentationParameters - Pointer to a D3DPRESENT_PARAMETERS structure. ppReturnedDeviceInterface - Address of a pointer to the returned IDirect3DDevice9 interface, which represents the created device. If the CreateDevice failed, then we try again using a 16-bit depth buffer. If that fails, we release our d3d9 object then show an error message. HRESULT hr = 0; hr = d3d9->CreateDevice( D3DADAPTER_DEFAULT, deviceType, hwnd, vertexproc, &d3dpp, d3dDevice); if( FAILED(hr) ) { d3dpp.AutoDepthStencilFormat = D3DFMT_D16; hr = d3d9->CreateDevice( D3DADAPTER_DEFAULT, deviceType, hwnd, vertexproc, &d3dpp, d3dDevice); if( FAILED(hr) ) { d3d9->Release(); MessageBox(0, "CreateDevice() - FAILED", 0, 0); return false; } } After our device has been created, we release our IDirect3D9 Object then return a success. d3d9->Release(); return true; } Now we release our IDirect3DDevice9 object by calling IDirect3DDevice9->Release(), then end the function by returning 0 for no errors. d3dDevice->Release(); return 0; } Now that were done with that, all thats left to do is modify the messageloop() function a bit to show our Direct3D content (which should just be a blue screen when we're done). The first new line in the messageloop() function is setting the lasttime to equal the current time. This is used to keep track of the time between frames. By doing this, we make sure our animations are on queue with the framerate. static float lastTime = (float)timeGetTime(); When there are no messages being processed, then there is idle processing. This is the time we want to display our frames. After the else statement, we add in new code. The first line is setting the current time. The second line is setting timeDelta. timeDelta is pretty much the frames per second. After that we display the goods using the display function we passed in in WinMain() (RenderScene()). Then we set the last time to equal the current time. else{ float currTime = (float)timeGetTime(); float timeDelta = (currTime - lastTime)*0.001f; display(timeDelta); lastTime = currTime; } After all that, the program should initialize Direct3D and display a blueish screen. Remember if you have any questions or comments or want to say anything at all, just contact us or go to the forums over at blue bounce(kinda poorly made, but its there if you need help or anything). I really hope you learned something from this. Now we get to go on to the REAL fun stuff! GRAPHICS!!! Here's the final code: main.cpp //Include necessary Headers// #include <windows.h> #include <d3d9.h> //Include the Direct3D library #pragma comment (lib, "d3d9.lib") //Define variables/constants// LPCTSTR WndClassName = "firstwindow"; //Define our window class name HWND hwnd = NULL; //Sets our windows handle to NULL const int Width = 800; //window width const int Height = 600; //window height //Pointer to Direct3D interface IDirect3D9* d3d9 = NULL; //pointer to Direct3D device class IDirect3DDevice9* d3dDevice = NULL; //Functions// bool SetupScene(); //Set up our Scene void CleanUpScene(); //Release memory bool RenderScene(); //Renders a single frame //Initialize window and Direct3D bool InitializeD3DWindow(HINSTANCE hInstance, int ShowWnd, int width, int height, bool windowed, D3DDEVTYPE deviceType, IDirect3DDevice9** d3dDevice); //Main part of the program int messageloop(bool (*display)(float timeDelta)); //Windows callback procedure, handles messages LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); bool SetupScene(){ //Set up our Scene >return true; } void CleanUpScene(){ //Release memory return; } bool RenderScene(float timeDelta) //Renders a single frame { if( d3dDevice ) { //Clear the window to 0x00000000 (black) d3dDevice->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000055, 1.0f, 0); //Start drawing our scene d3dDevice->BeginScene(); //Stop drawing our scene d3dDevice->EndScene(); //Display our newly created scene d3dDevice->Present(0, 0, 0, 0); } return true; } //Main windows function int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { //Initialize Direct3D// //If initialization failed, display an error message if(!InitializeD3DWindow(hInstance, nShowCmd, Width, Height, true, D3DDEVTYPE_HAL, &d3dDevice)) { // If initialization failed, display an error message MessageBox(0, "Window/D3D Initialization - Failed", "Error", MB_OK); return 0; } //Setup our scene if(!SetupScene()) { // If Setup failed, display error message MessageBox(0, "SetupScene() - FAILED", 0, 0); return 0; } //Jump into the heart of our program messageloop(RenderScene); //Release memory allocated by our SetupScene function CleanUpScene(); d3dDevice->Release(); //Release our Direct3D Device return 0; } //Initialize our Direct3D window bool InitializeD3DWindow(HINSTANCE hInstance, int ShowWnd, int width, int height, bool windowed, D3DDEVTYPE deviceType, IDirect3DDevice9** d3dDevice) { //Start creating the window// WNDCLASSEX wc; //Create a new extended windows class wc.cbSize = sizeof(WNDCLASSEX); //Size of our windows class wc.style = CS_HREDRAW | CS_VREDRAW; //class styles wc.lpfnWndProc = WndProc; //Default windows procedure function wc.cbClsExtra = NULL; //Extra bytes after our wc structure wc.cbWndExtra = NULL; //Extra bytes after our windows instance wc.hInstance = hInstance; //Instance to current application wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); //Title bar Icon wc.hCursor = LoadCursor(NULL, IDC_ARROW); //Default mouse Icon wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 2); //Window bg color wc.lpszMenuName = NULL; //Name of the menu attached to our window wc.lpszClassName = WndClassName; //Name of our windows class wc.hIconSm = LoadIcon(NULL, IDI_WINLOGO); //Icon in your taskbar if (!RegisterClassEx(&wc)) //Register our windows class { //If registration failed, display error MessageBox(NULL, "Error registering class", "Error", MB_OK | MB_ICONERROR); return 1; } HWND hwnd = CreateWindowEx( //Create our Extended Window NULL, //Extended style WndClassName, //Name of our windows class "Window Title", //Name in the title bar of our window WS_OVERLAPPEDWINDOW, //style of our window CW_USEDEFAULT, CW_USEDEFAULT, //Top left corner of window width, //Width of our window height, //Height of our window NULL, //Handle to parent window NULL, //Handle to a Menu hInstance, //Specifies instance of current program NULL //used for an MDI client window ); if (!hwnd) //Make sure our window has been created { //If not, display error MessageBox(NULL, "Error creating window", "Error", MB_OK | MB_ICONERROR); return 1; } ShowWindow(hwnd, ShowWnd); //Shows our window UpdateWindow(hwnd); //Its good to update our window //Start initializing Direct3D// //Start by initializing the Direct3D interface d3d9 = Direct3DCreate9( D3D_SDK_VERSION); if(!d3d9){ //If it was not initialized return false; } D3DCAPS9 caps; //Set the device capabilities structure to caps //Get the device capabilities d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, deviceType, &caps); int vertexproc = NULL; //Set our vertex processing to NULL //If we can use hardware vertex processing if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) //Set vertex processing to hardware vertexproc = D3DCREATE_HARDWARE_VERTEXPROCESSING; else //Set vertex processing to software vertexproc = D3DCREATE_SOFTWARE_VERTEXPROCESSING; D3DPRESENT_PARAMETERS d3dpp; //The width of the back buffer in pixels d3dpp.BackBufferWidth = width; //The height of the back buffer in pixels d3dpp.BackBufferHeight = height; //Back buffer pixel format d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; //Amount of back buffers d3dpp.BackBufferCount = 1; //The type of multisampling for the buffer d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; //The quality of multisampling d3dpp.MultiSampleQuality = NULL; //Specifies how buffers will be swapped d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; //Handle to our window d3dpp.hDeviceWindow = hwnd; //FullScreen or Windowed d3dpp.Windowed = windowed; //true lets Direct3D do the depth/stencil buffer automatically d3dpp.EnableAutoDepthStencil = true; //Auto depth/stencil buffer format d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; //Additional characteristics d3dpp.Flags = NULL; //Refresh rate d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; //Presentation Interval d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; HRESULT hr = 0; hr = d3d9->CreateDevice( D3DADAPTER_DEFAULT, // primary adapter deviceType, // device type hwnd, // window associated with device vertexproc, // vertex processing &d3dpp, // present parameters d3dDevice); // return created device if( FAILED(hr) ) //If there was a problem creating the device { // try again using a 16-bit depth buffer d3dpp.AutoDepthStencilFormat = D3DFMT_D16; hr = d3d9->CreateDevice( D3DADAPTER_DEFAULT, deviceType, hwnd, vertexproc, &d3dpp, d3dDevice); if( FAILED(hr) ) //If it still fails { d3d9->Release(); // done with d3d9 object MessageBox(0, "CreateDevice() - FAILED", 0, 0); return false; } } d3d9->Release(); // done with d3d9 object return true; //If there were no errors, return true } int messageloop(bool (*display)(float timeDelta)){ //The message loop MSG msg; //Create a new message structure ZeroMemory(&msg, sizeof(MSG)); //clear message structure to NULL //Set the last time. Keeps track of time between frames static float lastTime = (float)timeGetTime(); while(TRUE) //While there is a message { //If there was a windows message if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) //If the message was WM_QUIT break; //Exit the message loop TranslateMessage(&msg); //Translate the message //Send the message to default windows procedure DispatchMessage(&msg); } else{ //Otherewise, keep the flow going //Set the time float currTime = (float)timeGetTime(); //Set the speed until the next frame float timeDelta = (currTime - lastTime)*0.001f; display(timeDelta); //Display the goods //Last time equal to current time lastTime = currTime; } } if(d3d9){ //If there is a Direct3D device d3d9->Release(); //Release Memory from the device d3d9=NULL; //Make sure there is no memory leaks } return (int)msg.wParam; //Return the message } LRESULT CALLBACK WndProc(HWND hwnd, //Default windows procedure UINT msg, WPARAM wParam, LPARAM lParam) { switch( msg ) //Check message { case WM_KEYDOWN: //For a key down //If escape key was pressed, display popup box if( wParam == VK_ESCAPE ){ if(MessageBox(0, "Are you sure you want to exit?", "Really?", MB_YESNO | MB_ICONQUESTION) == IDYES) //Release the windows allocated memory DestroyWindow(hwnd); } return 0; case WM_DESTROY: //If x button in top right was pressed PostQuitMessage(0); return 0; } //return the message for windows to handle it return DefWindowProc(hwnd, msg, wParam, lParam); }
Sign in to comment