// Direct3D Immediate Mode by Bipin Patwardhan
// Full Source Listing

#define INITGUID

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <string.h>
#include <search.h>
#include <ddraw.h>
#include <d3drmwin.h>
#include <d3d.h>
#include <d3dtypes.h>

#include "d3dmacs.h"

#define MAX_DRIVERS 5
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
#define BIT_DEPTH 8
#define RELEASE(x) if (x != NULL) {x->Release(); x = NULL;}
#define NUM_VERTICES 3
#define NUM_TRIANGLES 1

typedef struct t_glob
{
    HWND hWnd; HINSTANCE hInstance;
    // current shade mode, fill mode and lighting state
    D3DRMRENDERQUALITY RenderQuality;
    LPDIRECTDRAWSURFACE lpDDSFront, lpDDSBack;
    LPDIRECT3DDEVICE dev;     // device
    LPDIRECT3DVIEWPORT view;  // viewport through which we view
    LPDIRECT3DEXECUTEBUFFER lpD3DExBuf; 
    GUID DriverGUID[MAX_DRIVERS];  // GUIDs of available D3D drivers
    char DriverName[MAX_DRIVERS][50]; // names of available D3D drivers
    int NumDrivers;  // number of available D3D drivers
    int  curDriver;  // number of D3D driver currently being used
    int BPP; int binitDone; int bquit;
} glob;

// Global Variables
glob globals;
LPDIRECT3DMATERIAL lpBmat, lpMat1; LPDIRECTDRAW lpDD;
D3DEXECUTEDATA d3dExData; LPDIRECT3DEXECUTEBUFFER lpD3DExBuf;
D3DMATRIXHANDLE hViewPos, hView, hViewRot;
D3DMATRIX viewpos = {
    D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(10.0), D3DVAL(1.0)
};
D3DMATRIX viewrot, view, dviewrot, dworld;
D3DMATRIX identity = {
    D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0)
};

int APIENTRY WinMain(HMODULE hInstance, HMODULE hNull,
    PSTR lpszCmdLine, int nCmdShow) {
    MSG msg; int failcount = 0;

    InitGlobals();

    InitWindows(hInstance, hNull, lpszCmdLine, nCmdShow);

    InitDirectDraw(globals.hWnd);

    ShowWindow(globals.hWnd, SW_SHOWNORMAL);
    UpdateWindow(globals.hWnd);
    globals.bquit = FALSE;

    while ( !globals.bquit ) {
        while ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ) {
            if ( msg.message == WM_QUIT ) {
                CleanupAndPostQuit(); break;
            }

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        if ( !globals.bquit ) RenderLoop();
    }

    return msg.wParam;
}

BOOL InitWindows(HMODULE hInstance, HMODULE hnull, PSTR lpszCmd, int nCmdShow)
{
    WNDCLASS wc; HWND hWnd; HDC hdc;

    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0; wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = "Immediate";

    RegisterClass(&wc);

    hWnd = CreateWindow("Immediate", "Direct3D Immediate Mode",
        WS_OVERLAPPEDWINDOW, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT,
        NULL, NULL, hInstance, NULL);

    globals.hWnd = hWnd; globals.hInstance = hInstance;

    hdc = GetDC(globals.hWnd);
    globals.BPP = GetDeviceCaps(hdc, BITSPIXEL);
    ReleaseDC(hWnd, hdc);

    return TRUE;
}

BOOL InitDirectDraw(HWND hWnd) {
    DirectDrawCreate(NULL, &lpDD, NULL);
    lpDD->SetCooperativeLevel(hWnd,
        DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
    lpDD->SetDisplayMode(SCREEN_WIDTH, SCREEN_HEIGHT, BIT_DEPTH);
    CreateSurface(hWnd, lpDD);
    InitDirect3D(lpDD);
    return TRUE;
}

BOOL CreateSurface(HWND hWnd, LPDIRECTDRAW lpDD) {
    LPDIRECTDRAWSURFACE lpDDSFront, lpDDSZ;
    DDSURFACEDESC ddsd;
    DDSCAPS ddscaps; HRESULT ret_val;

    // First Obtain the primary surface
    memset(&ddsd, 0, sizeof(DDSURFACEDESC));
    ddsd.dwSize = sizeof(DDSURFACEDESC);
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP
        | DDSCAPS_COMPLEX | DDSCAPS_3DDEVICE;
    ddsd.dwBackBufferCount = 1;

    lpDD->CreateSurface(&ddsd, &lpDDSFront, NULL);

    // Get the back buffer
    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
    lpDDSFront->GetAttachedSurface(&ddscaps, &(globals.lpDDSBack));

    // Create the Z buffer and attach it to the back surface
    memset(&ddsd, 0, sizeof(DDSURFACEDESC));
    ddsd.dwSize = sizeof(DDSURFACEDESC);
    ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS
        | DDSD_ZBUFFERBITDEPTH;
    ddsd.dwWidth = SCREEN_WIDTH;
    ddsd.dwHeight = SCREEN_HEIGHT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_ZBUFFER;
    ddsd.dwZBufferBitDepth = BIT_DEPTH;
    lpDD->CreateSurface(&ddsd, &lpDDSZ, NULL);
    globals.lpDDSBack->AddAttachedSurface(lpDDSZ);
    globals.lpDDSFront = lpDDSFront;

    return TRUE;
}

HRESULT WINAPI enumDeviceFunc(LPGUID lpGuid, LPSTR lpDeviceDescription,
        LPSTR lpDeviceName, LPD3DDEVICEDESC lpHWDesc,
    LPD3DDEVICEDESC lpHELDesc, LPVOID lpContext) {
    static BOOL hardware = FALSE; static BOOL mono = FALSE;     
    LPD3DDEVICEDESC lpDesc;
    int *lpStartDriver = (int *)lpContext;

    lpDesc = lpHWDesc->dcmColorModel ? lpHWDesc : lpHELDesc;
    
    if (!(lpDesc->dwDeviceRenderBitDepth & BPPToDDBD(globals.BPP)))     
        return D3DENUMRET_OK;

    memcpy(&globals.DriverGUID[globals.NumDrivers], lpGuid, sizeof(GUID));
    lstrcpy(&globals.DriverName[globals.NumDrivers][0], lpDeviceName);
    if (*lpStartDriver == -1) {
        *lpStartDriver = globals.NumDrivers;
        hardware = lpDesc == lpHWDesc ? TRUE : FALSE;
        mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE;
    } else if (lpDesc == lpHWDesc && !hardware) {
        *lpStartDriver = globals.NumDrivers;
        hardware = lpDesc == lpHWDesc ? TRUE : FALSE;
        mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE;
    } else if ((lpDesc == lpHWDesc && hardware ) 
        || (lpDesc == lpHELDesc && !hardware)) {
        if (lpDesc->dcmColorModel == D3DCOLOR_MONO && !mono) {
            *lpStartDriver = globals.NumDrivers;
            hardware = lpDesc == lpHWDesc ? TRUE : FALSE;
            mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE;
        }
    }
    globals.NumDrivers++;
    
    if (globals.NumDrivers == MAX_DRIVERS) return (D3DENUMRET_CANCEL);
    
    return (D3DENUMRET_OK);
}

BOOL InitDirect3D(LPDIRECTDRAW lpDD) {
    LPDIRECT3D lpD3D;
    LPGUID curDriver; LPDIRECT3DDEVICE lpD3DDev;
    D3DVIEWPORT viewData = { 0, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT,
        1, 1, 300, 300, 300 };

    viewData.dwSize = sizeof(D3DVIEWPORT);

    // Get Direct3D interface
    lpDD->QueryInterface(IID_IDirect3D, (void**)&lpD3D);

    // Get the current driver
    globals.curDriver = -1;
    lpD3D->EnumDevices(enumDeviceFunc, &globals.curDriver);

    // Now create the device
    globals.lpDDSBack->QueryInterface(globals.DriverGUID[0], 
        (void**)&lpD3DDev);

    globals.dev = lpD3DDev;

    lpD3D->CreateViewport(&globals.view, NULL);

    globals.dev->AddViewport(globals.view);
    globals.view->SetViewport(&viewData);

    CreateLightSources(lpD3D);

    // CreateExecuteBuffer();
    InitView(lpDD, lpD3D, globals.dev, globals.view);

    lpD3D->Release();
    
    return TRUE;
}

void CreateLightSources(LPDIRECT3D lpD3D) {
    LPDIRECT3DLIGHT lpD3DLight;
    D3DLIGHT dlLight;

    lpD3D->CreateLight(&lpD3DLight, NULL);

    memset(&dlLight, 0, sizeof(D3DLIGHT));
    dlLight.dwSize = sizeof(D3DLIGHT);
    dlLight.dltType = D3DLIGHT_DIRECTIONAL;
    dlLight.dcvColor.r = dlLight.dcvColor.g = D3DVAL(0.0);
    dlLight.dcvColor.b = D3DVAL(0.0);
    dlLight.dvPosition.x = dlLight.dvPosition.y = D3DVAL(0.0);
    dlLight.dvPosition.z = D3DVAL(-100.0);
    dlLight.dvDirection.x = dlLight.dvDirection.y = D3DVAL(0.0);
    dlLight.dvDirection.z = D3DVAL(1.0);

    lpD3DLight->SetLight(&dlLight);
    globals.view->AddLight(lpD3DLight);
}

BOOL CreateExecuteBuffer() {
    D3DEXECUTEBUFFERDESC debDesc;
    DWORD dwSize;

    dwSize =  sizeof(D3DVERTEX) * 3;
    dwSize += sizeof(D3DINSTRUCTION) * 2;
    dwSize += sizeof(D3DTRIANGLE);

    memset(&debDesc, 0, sizeof(D3DEXECUTEBUFFERDESC));
    debDesc.dwSize = sizeof(D3DEXECUTEBUFFERDESC);
    debDesc.dwFlags = D3DDEB_BUFSIZE; debDesc.dwBufferSize = dwSize;

    LPD3DINSTRUCTION lpIns;

    globals.dev->CreateExecuteBuffer(&debDesc, &lpD3DExBuf, NULL);
    ret_val = lpD3DExBuf->Lock(&debDesc);

    LPBYTE lpBuf; lpBuf = (LPBYTE)debDesc.lpData;
    
    memset(lpBuf, 0, dwSize);
    
    // Now set up all the vertices and add them 
    LPD3DVERTEX lpV;

    lpV = (LPD3DVERTEX)lpBuf;

    //  Vertex 1
    lpV[0].x = lpV[0].z = D3DVAL(0.0); lpV[0].y = D3DVAL(1.0);
    lpV[0].nx = lpV[0].nz = D3DVAL(0.0); lpV[0].ny = D3DVAL(1.0);
    lpV[0].tu = lpV[0].tv = D3DVAL(0.0);

    //  Vertex 2
    lpV[1].x = lpV[1].y = D3DVAL(1.0); lpV[1].z = D3DVAL(0.0);
    lpV[1].nx = lpV[1].ny = D3DVAL(1.0); lpV[1].nz = D3DVAL(0.0);
    lpV[1].tu = lpV[1].tv = D3DVAL(0.0);

    //  Vertex 3
    lpV[2].x = D3DVAL(1.0); lpV[2].y = lpV[2].z = D3DVAL(0.0);
    lpV[2].nx = D3DVAL(1.0); lpV[2].ny = lpV[2].nz = D3DVAL(0.0);
    lpV[2].tu = lpV[2].tv = D3DVAL(0.0);
    
    LPD3DTRIANGLE lpTri; LPD3DSTATE lpSt; LPBYTE lpStart;

    lpStart = lpBuf + (sizeof(D3DVERTEX) * 3);

    lpIns = (LPD3DINSTRUCTION)lpStart;
    lpIns->bOpcode = D3DOP_TRIANGLE;
    lpIns->bSize = sizeof(D3DTRIANGLE); lpIns->wCount = 1;

    lpTri = (LPD3DTRIANGLE) &lpIns[1];
    lpTri->v1 = 0; lpTri->v2 = 1; lpTri->v3 = 2;
    lpTri->wFlags = D3DTRIFLAG_EDGEENABLETRIANGLE;

    LPD3DINSTRUCTION lpExit;
    lpExit = (LPD3DINSTRUCTION)&lpTri[1];
    lpExit->bOpcode = D3DOP_EXIT;
    lpExit->bSize = 0; lpExit->wCount = 0;

    lpD3DExBuf->Unlock();

    memset(&d3dExData, 0, sizeof(D3DEXECUTEDATA));
    d3dExData.dwSize = sizeof(D3DEXECUTEDATA);
    d3dExData.dwInstructionOffset = (ULONG) 0;
    d3dExData.dwInstructionLength =
        (ULONG) ((char *)(&(lpExit[1])) - (char*)lpBuf);
    lpD3DExBuf->SetExecuteData(&d3dExData);

    globals.dev->BeginScene();
    globals.dev->Execute(lpD3DExBuf, globals.view, D3DEXECUTE_UNCLIPPED);
    globals.dev->EndScene();
 
    return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg) {
    case WM_ACTIVATE: break;
    case WM_DESTROY: CleanupAndPostQuit(); break;
    default: return DefWindowProc(hWnd, msg, wParam, lParam);
    }

    return 0L;
}

void CleanupAndPostQuit() {
    globals.bquit = TRUE;
    lpDD->Release();
    lpD3DExBuf->Release();
}

DWORD BPPToDDBD(int bpp) {
    switch(bpp) {
        case 1: return DDBD_1;
        case 2: return DDBD_2;
        case 4: return DDBD_4;
        case 8: return DDBD_8;
        case 16: return DDBD_16;
        case 24: return DDBD_24;
        case 32: return DDBD_32;
        default: return 0;
    }
}

BOOL RenderLoop() {
    HRESULT ret_val;
    D3DRECT rect = {0, 0, 400, 400};

    globals.view->Clear(1, &rect, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER);

    globals.dev->BeginScene();
    globals.dev->Execute(lpD3DExBuf, globals.view, D3DEXECUTE_UNCLIPPED);
    
    globals.dev->EndScene();

    lpD3DExBuf->GetExecuteData(&d3dExData);
    
    globals.lpDDSFront->Flip(globals.lpDDSBack, DDFLIP_WAIT);

    return TRUE;
}

void InitGlobals() {
    memset(&globals, 0, sizeof(globals));
    globals.bquit = FALSE;
}

BOOL InitView(LPDIRECTDRAW lpDD, LPDIRECT3D lpD3D,
        LPDIRECT3DDEVICE lpDev, LPDIRECT3DVIEWPORT lpView) {
    LPVOID lpBufStart, lpInsStart, lpPointer;
    D3DEXECUTEBUFFERDESC debDesc;
    size_t size; D3DTLVERTEX src_v[NUM_VERTICES];
    int t[8][3] = { 0, 1, 2, };
    D3DMATERIAL bmat, mat; D3DMATERIALHANDLE hBmat, hMat1;

    MAKE_MATRIX(lpDev, hViewRot, identity);
    MAKE_MATRIX(lpDev, hView, identity);
    MAKE_MATRIX(lpDev, hViewPos, identity);

    lpD3D->CreateMaterial(&lpBmat, NULL);
    
    memset(&bmat, 0, sizeof(D3DMATERIAL));
    bmat.dwSize = sizeof(D3DMATERIAL);
    bmat.diffuse.r = bmat.diffuse.g = (D3DVALUE)0.0;
    bmat.diffuse.b = (D3DVALUE)1.0;
    bmat.ambient.r = bmat.ambient.g = (D3DVALUE)0.0;
    bmat.ambient.b = (D3DVALUE)1.0;
    // bmat.hTexture = TextureHandle[0];
    bmat.dwRampSize = 1;
    lpBmat->SetMaterial(&bmat);
    lpBmat->GetHandle(lpDev, &hBmat);
    lpView->SetBackground(hBmat);

    lpD3D->CreateMaterial(&lpMat1, NULL);
    memset(&mat, 0, sizeof(D3DMATERIAL));
    mat.dwSize = sizeof(D3DMATERIAL);
    mat.diffuse.r = (D3DVALUE)1.0;
    mat.diffuse.g = mat.diffuse.b = (D3DVALUE)0.0;
    mat.ambient.r = (D3DVALUE)1.0;
    mat.ambient.g = mat.ambient.b = (D3DVALUE)0.0;
// #define SPECULAR
#ifdef SPECULAR
    mat.specular.r = (D3DVALUE)1.0;
    mat.specular.g = mat.specular.b = (D3DVALUE)0.0;
    mat.power = (float)40.0;
#else
    mat.specular.r = mat.specular.g = mat.specular.b = (D3DVALUE)0.0;
    mat.power = (float)0.0;
#endif    
    // mat.hTexture = TextureHandle[1];
    mat.dwRampSize = 1;
    lpMat1->SetMaterial(&mat);
    lpMat1->GetHandle(lpDev, &hMat1);
    /* Setup vertices */
    memset(&src_v[0], 0, sizeof(D3DVERTEX) * NUM_VERTICES);
    /* V 0 */
    src_v[0].sx = src_v[0].sy = D3DVAL(10.0); src_v[0].sz = D3DVAL(0.1);
    src_v[0].rhw = D3DVAL(1.0);
    src_v[0].color = RGBA_MAKE(255, 0, 0, 0);
    src_v[0].specular = RGB_MAKE(0, 0, 255);
    src_v[0].tu = src_v[0].tv = D3DVAL(0.0);
    /* V 1 */
    src_v[1].sx = D3DVAL(300.0); src_v[1].sy = D3DVAL(50.0);
    src_v[1].sz = D3DVAL(0.9); src_v[1].rhw = D3DVAL(1.0);
    src_v[1].color = RGBA_MAKE(0, 255, 0, 0);
    src_v[1].specular = RGB_MAKE(0, 0, 0);
    src_v[1].tu = src_v[1].tv = D3DVAL(1.0);
    /* V 2 */
    src_v[2].sx = D3DVAL(150.0); src_v[2].sy = D3DVAL(180.0);
    src_v[2].sz = D3DVAL(0.6); src_v[2].rhw = D3DVAL(1.0);
    src_v[2].color = RGBA_MAKE(0, 0, 255, 0);
    src_v[2].specular = RGB_MAKE(0, 0, 0);
    src_v[2].tu = D3DVAL(0.0); src_v[2].tv = D3DVAL(1.0);
    /* Create an execute buffer */
    size = sizeof(D3DVERTEX) * NUM_VERTICES;
    size += sizeof(D3DINSTRUCTION) * 6;
    size += sizeof(D3DSTATE) * 2;
    size += sizeof(D3DPROCESSVERTICES);
    size += sizeof(D3DTRIANGLE) * 1;
    memset(&debDesc, 0, sizeof(D3DEXECUTEBUFFERDESC));
    debDesc.dwSize = sizeof(D3DEXECUTEBUFFERDESC);
    debDesc.dwFlags = D3DDEB_BUFSIZE;
    debDesc.dwBufferSize = size;
    lpDev->CreateExecuteBuffer(&debDesc, &lpD3DExBuf, NULL);
    lpD3DExBuf->Lock(&debDesc);
    lpBufStart = debDesc.lpData;
    memset(lpBufStart, 0, size);
    lpPointer = lpBufStart;

    /* Copy vertices to execute buffer */
    VERTEX_DATA(&src_v[0], NUM_VERTICES, lpPointer);
    /* Setup instructions in execute buffer */
    lpInsStart = lpPointer;
    OP_STATE_TRANSFORM(1, lpPointer);
        STATE_DATA(D3DTRANSFORMSTATE_VIEW, hView, lpPointer);
    OP_STATE_LIGHT(1, lpPointer);
        STATE_DATA(D3DLIGHTSTATE_MATERIAL, hMat1, lpPointer);
    OP_PROCESS_VERTICES(1, lpPointer);
        PROCESSVERTICES_DATA(D3DPROCESSVERTICES_COPY |
            D3DPROCESSVERTICES_UPDATEEXTENTS, 0, NUM_VERTICES,
            lpPointer);
    if (QWORD_ALIGNED(lpPointer)) OP_NOP(lpPointer);
    OP_TRIANGLE_LIST(1, lpPointer);
        ((LPD3DTRIANGLE)lpPointer)->v1 = 0;
        ((LPD3DTRIANGLE)lpPointer)->v2 = 1;
        ((LPD3DTRIANGLE)lpPointer)->v3 = 2;
        ((LPD3DTRIANGLE)lpPointer)->wFlags = D3DTRIFLAG_EDGEENABLETRIANGLE;
        lpPointer = ((char*)lpPointer) + sizeof(D3DTRIANGLE);
    OP_EXIT(lpPointer);
    /* Setup the execute data */
    lpD3DExBuf->Unlock();
    memset(&d3dExData, 0, sizeof(D3DEXECUTEDATA));
    d3dExData.dwSize = sizeof(D3DEXECUTEDATA);
    d3dExData.dwVertexCount = NUM_VERTICES;
    d3dExData.dwInstructionOffset =
        (ULONG) ((char *)lpInsStart - (char *)lpBufStart);
    d3dExData.dwInstructionLength =
        (ULONG) ((char *)lpPointer - (char *)lpInsStart);
    lpD3DExBuf->SetExecuteData(&d3dExData);

    return TRUE;
}

BOOL TickScene(LPDIRECT3DDEVICE lpDev, LPDIRECT3DVIEWPORT lpView) {
    static int dir = 1;

    if (viewpos._43 < D3DVAL(4.0)) dir = 0;
    if (viewpos._43 > D3DVAL(12.0)) dir = 1;
    if (dir) viewpos._43 -= D3DVAL(0.4);
    else viewpos._43 += D3DVAL(0.4);
    if (lpDev->SetMatrix(hViewPos, &viewpos) != D3D_OK) return FALSE;
    return TRUE;
}