In this exercise, you will modify the first example that you created to include a shaded triangle on the device. The triangle is the fundamental unit of three-dimensional graphic designs. While GAPI provides low-level access to the frame buffer, the creation of triangles in Direct3D Mobile uses inherent constructs of the API. Triangles and other primitive types are native additions to the Direct3D Mobile technology and provide high-level graphics tools that will be new to the GAPI developer. These primitive types can be used for both two-dimensional and three dimensional scene generations.
-
Return to the top of the createdevice.cpp file, and then notice the remaining declarations. There is an LPDIRECT3DMOBILEVERTEXBUFFER global variable named g_pVB. The global variable g_pVB serves as an interface reference to a buffer to hold the drawing vertices. There is a structure named CUSTOMVERTEX that is used to describe the coordinates and color of the drawing vertices. Finally, there is the D3DMFVF_CUSTOMVERTEX macro. This macro specifies the flexible vertex format (FVF) parameters that you will use when creating the vertex buffer later in this exercise.
-
Locate the InitVB function that has been started for you. This function creates a Vertex Buffer (VB) to hold the vertices that this example uses for its triangle creation.
The InitVB function begins by declaring four variables, and then declares and initializes a CUSTOMVERTEX array named vertices.
The InitVB function calls the g_pd3dmDevice GetDeviceCaps method to retrieve the device's capabilities, storing the results in the D3DMCAPS structure named caps, and returns E_FAIL if the GetDeviceCaps call fails.
The function performs a bitwise AND operation on the SurfaceCaps property of the caps structure and the D3DMSURFCAPS_VIDVERTEXBUFFER identifier. If this condition evaluates to true, indicating that the D3DMSURFCAPS_VIDVERTEXBUFFER bit is set, the device supports creating vertex buffers in video memory; otherwise, the vertex buffer will have to be created in system memory. Video memory provides better performance than system memory, so the application uses video memory if the device supports it. The D3DMPOOL variable named pool indicates which type of memory to use when creating the vertex buffer. If the device supports video memory, the function assigns the enumeration value D3DMPOOL_VIDEOMEM to the pool variable; otherwise, the function assigns the enumeration value D3DMPOOL_SYSTEMMEM.
-
Immediately following the line that assigns the D3DMPOOL_SYSTEMMEM value to the pool variable (outside the prior if...else condition), call the g_pd3dmDevice >CreateVertexBuffer method, storing the result in the hResult variable that was predeclared, as shown in the following code example.
hResult = g_pd3dmDevice->CreateVertexBuffer(
3*sizeof(CUSTOMVERTEX), 0,
D3DMFVF_CUSTOMVERTEX, pool, &g_pVB);
Pass three times the size of the CUSTOMVERTEX structure (because there are three vertices) as the first parameter, indicating to the device to allocate enough vertex buffer memory to hold a CUSTOMVERTEX array with three elements.
As the second parameter, pass 0. This usage flag setting is not used in this lab.
Pass the predeclared D3DMFVF_CUSTOMVERTEX macro as the third parameter, describing the vertex format.
As the fourth parameter, pass the pool variable to tell the function which memory pool to use for storing the vertex: video memory or system memory.
An interface reference to the resulting vertex buffer is stored in the fifth parameter, so pass a reference to the predeclared g_pVB global variable. This parameter is set only when the CreateVertexBuffer function returns successfully.
-
Add an if statement that uses the FAILED macro to test the hResult value, and return the E_FAIL value if the CreateVertexBuffer method call fails, as shown in the following code example.
if (FAILED(hResult))
return E_FAIL;
Now that the vertex buffer has been created, you need to fill it. To fill the vertex buffer, you need to lock the vertex buffer to gain access to the vertices because the vertex buffer may be in memory that is accessible by other processes.
-
Add a call to the g_pVB Lock method, storing the result in the hResult variable, as shown in the following code example.
hResult = g_pVB->Lock(0, sizeof(vertices), &pVertices, 0);
Pass 0 as the first parameter, which is an offset into the vertex to be locked.
As the second parameter, pass the size of the vertices array to indicate how many bytes are to be locked.
Pass a reference to the predeclared pVertices pointer as the third parameter. Upon the successful return of the Lock method, pVertices will contain the address of the locked vertex buffer memory.
Pass 0 as the final parameter; this flags parameter is not used in this lab.
Note: |
|---|
|
Similar to many graphics implementations (such as GAPI), there is a discrete concept of locking and unlocking the buffer, essentially raising and lowering a flag to other processes, that indicates that the buffer is being written to. If this approach were not used, race conditions could result during memory modification, and it could ultimately lead to memory corruption.
|
-
Add an if statement that uses the FAILED macro on the hResult value, and return the E_FAIL value if the Lock method call failed, as shown in the following code example.
if (FAILED(hResult))
return E_FAIL;
-
Store the list of vertices in the vertex buffer by using memcpy to copy the contents of the vertices array to the address identified by the pVertices pointer, and then unlock the vertex buffer memory by calling the g_pVB >Unlock method, as shown in the following code example.
memcpy(pVertices, vertices, sizeof(vertices));
g_pVB->Unlock();
The last line of the function has been typed for you. It returns the S_OK identifier value.
-
Verify that the completed InitVB function looks like the following code example.
HRESULT InitVB()
{
D3DMCAPS caps;
D3DMPOOL pool;
void* pVertices;
HRESULT hResult;
CUSTOMVERTEX vertices[] =
{
{ 150.0f, 50.0f, 0.5f, 1.0f, 0x00000000 },
{ 250.0f, 250.0f, 0.5f, 1.0f, 0xff00ff00 },
{ 50.0f, 250.0f, 0.5f, 1.0f, 0xff00ffff }
};
if (FAILED(g_pd3dmDevice->GetDeviceCaps(&caps)))
return E_FAIL;
if (caps.SurfaceCaps & D3DMSURFCAPS_VIDVERTEXBUFFER)
pool = D3DMPOOL_VIDEOMEM;
else
pool = D3DMPOOL_SYSTEMMEM;
hResult = g_pd3dmDevice->CreateVertexBuffer(
3*sizeof(CUSTOMVERTEX), 0,
D3DMFVF_CUSTOMVERTEX, pool, &g_pVB);
if (FAILED(hResult))
return E_FAIL;
hResult = g_pVB->Lock(0, sizeof(vertices), &pVertices, 0);
if (FAILED(hResult))
return E_FAIL;
memcpy(pVertices, vertices, sizeof(vertices));
g_pVB->Unlock();
return S_OK;
}
You need to modify the Render function, so it will not only draw the background as it did in the first example, but it will also draw the triangle that you are creating.
-
In the Render function, locate the line after the call to the BeginScene method that reads if (SUCCEEDED(hResult)).
-
Immediately following the link in Step 9, create a new line and type an opening { brace because you will be adding more statements inside the conditional if block to be run when the BeginScene call succeeds.
You will be passing the vertices down a stream, so first you need to specify the source of that stream, which is the vertex buffer.
-
Create another new line after the { brace you just typed. The new line should be before the EndScene method call. On the new line, call the g_pd3dmDevice SetStreamSource method, as shown in the following code example.
g_pd3dmDevice->SetStreamSource(0, g_pVB, sizeof(CUSTOMVERTEX));
The first parameter identifies the number of the data stream to use. The only value that Direct3D Mobile supports is 0.
As the second parameter, pass the g_pVB vertex buffer whose data will be used to render the triangle.
As the final parameter, pass the size of the CUSTOMVERTEX structure.
-
Call the g_pd3dmDevice >DrawPrimitive method that does the actual rendering of the geometric primitive, as shown in the following code example.
g_pd3dmDevice->DrawPrimitive(D3DMPT_TRIANGLELIST, 0, 1);
The first parameter indicates which geometric primitive to draw. Passing the enumeration value D3DMPT_TRIANGLELIST as the first parameter indicates that you want to draw one or more triangles. The vertices used to draw the geometry come from stream number 0, which is bound to the video buffer by the earlier call to the SetStreamSource method. Each primitive uses a predefined number of vertices. In the case of a triangle, each triangle uses three vertices: a line uses two vertices and a point uses one.
The second parameter indicates the offset into the vertex buffer of the first vertex to use. In this lab, pass 0 to indicate that drawing should start with the first vertex.
The last parameter indicates the number of primitive geometries to render. It is the developer's responsibility to insure that enough vertices are available in the vertex buffer to satisfy the number of geometries being rendered. For example, drawing one triangle requires that at least three vertices be in the vertex buffer; drawing two triangles requires that at least six vertices be in the vertex buffer, and so on. In this lab, pass a 1 for this parameter indicating that the DrawPrimitive method should draw one triangle.
-
Immediately following the line that calls the EndScene method, terminate the if block by creating a new line and typing a closing } brace. (The remaining call to the Present method should still be outside the if block.)
-
Verify that the updated Render function looks like the following code example.
VOID Render()
{
if (g_pd3dmDevice == NULL)
return;
g_pd3dmDevice->Clear(0, NULL, D3DMCLEAR_TARGET,
D3DMCOLOR_XRGB(0,0,255), 1.0f, 0);
j
HRESULT hResult = g_pd3dmDevice->BeginScene();
if (SUCCEEDED(hResult))
{
g_pd3dmDevice->SetStreamSource(0, g_pVB, sizeof(CUSTOMVERTEX));
g_pd3dmDevice->DrawPrimitive(D3DMPT_TRIANGLELIST, 0, 1);
g_pd3dmDevice->EndScene();
}
g_pd3dmDevice->Present(NULL, NULL, NULL, NULL);
}
You must also modify the WinMain function, so it creates the vertex buffer and launches the new program logic.
-
Locate the line in the WinMain function that calls the ShowWindow function. You will be enclosing that statement and all of the statements through the while loop in a condition that depends on the success of a call to the InitVB function, so immediately before the line that calls ShowWindow, create a new line.
-
On the new line, call the InitVB function, and then assign its return value to the hResult variable, as shown in the following code example.
-
Add an if statement (still before the ShowWindow function call), using the FAILED macro, to test the hResult value returned by the InitVB function. If the InitVB function call fails, return the E_FAIL value, as shown in the following code example.
if (FAILED(hResult))
return E_FAIL;
-
Locate and modify the while condition a few lines down to negatively compare the message property of the msg instance to the WM_QUIT message. In other words, you want the while block to loop as long as the message does not indicate that the application should terminate, as shown in the following code example.
while (msg.message != WM_QUIT)
-
Inside the while loop and before the TranslateMessage function call, add an if statement that calls the PeekMessage function. Be sure to include the opening { brace because you'll be enclosing the TranslateMessage and DispatchMessage lines in the if block, as shown in the following code example.
if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
{
First, pass a reference to the msg variable. The msg variable receives the currently queued message information upon successful return.
The second parameter should be NULL, indicating that messages for any window in the current thread should be retrieved.
The minimum and maximum message filters should both be 0U.
Pass the PM_REMOVE identifier to indicate that all messages should be removed from the queue after retrieval (except WM_PAINT, which is always processed before removal).
-
After the existing DispatchMessage function call, create a new line and terminate the if block with a closing } brace.
-
On the next line, create an else block corresponding to the if block you just added. As its single instruction, on the next line, call the Render function, as shown in the following code example.
Note: |
|---|
|
You may want to clean up the indentation of your code at this point, if necessary. An easy way to clean up indentation is to highlight the code, and then click Edit | Advanced | Format Selection in Visual Studio.
|
-
Verify that the updated WinMain function looks like the following code example.
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPTSTR szCmd, INT )
{
HRESULT hResult;
if (IsScreenRotated())
return 0;
WNDCLASS wc = { 0L, MsgProc, 0L, 0L,
hInst, NULL, NULL, NULL, NULL,
TEXT("D3DM Tutorial") };
RegisterClass( &wc );
int iScreenWidth = GetSystemMetrics(SM_CXSCREEN);
int iScreenHeight = GetSystemMetrics(SM_CYSCREEN);
HWND hWnd = CreateWindow(TEXT("D3DM Tutorial"),
TEXT("D3DM Tutorial 01: CreateDevice"),
WS_VISIBLE,
0, 0, iScreenWidth, iScreenHeight,
NULL, NULL, wc.hInstance, NULL);
SHFullScreen(hWnd, SHFS_HIDESIPBUTTON | SHFS_HIDETASKBAR);
hResult = InitD3DM(hWnd);
if (FAILED(hResult))
return E_FAIL;
hResult = InitVB();
if (FAILED(hResult))
return E_FAIL;
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
MSG msg = {0};
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
Render();
}
return 0;
}
To build the project and run the application