Friday, July 13, 2012

Open Asset Import Library (AssImp) is great!

I can only recommend you to try it out, it has a very simple and well structured API and supports many different formats, including COLLADA.
You don't even need to compile the whole thing, just download the package from sourceforge, add the include path and dynamically link to the provided DLL (you will only need to import aiImportFile, aiReleaseImport and some of the material getter functions).
I use AssImp in my little converter tool to translate COLLADA files to a custom binary format.

Here is a small snippet to dump simple meshes (separate binary files for vertex attributes and indices):

(some helper code first)
class ExportFile {
public:
    FILE* f;
 
    ExportFile(const char* filename) { f = fopen(filename, "wb"); }
    ~ExportFile() { fclose(f); }
    inline void writeU32(uint32 value) { fwrite(&value, 1, sizeof(uint32), f); }
    inline void write(void* data, size_t length) { fwrite(data, 1, length, f); }
 // etc.
};

void writeArray(const char* filename, unsigned int count, size_t stride, void* data)
{
    ExportFile f(filename);
    f.writeU32(count);
    f.write(data, count*stride);
}
Here's the export function:
void exportMesh(aiMesh* mesh)
{
    std::string meshName = meshNodeNames[mesh];

 // vertex arrays
    writeArray((meshName + std::string(".vtx")).c_str(), mesh->mNumVertices,
               sizeof(aiVector3D), mesh->mVertices);
    writeArray((meshName + std::string(".nrm")).c_str(), mesh->mNumVertices, 
                sizeof(aiVector3D), mesh->mNormals);

    if (mesh->mTangents) {
        writeArray((meshName + std::string(".tan")).c_str(), mesh->mNumVertices, 
                   sizeof(aiVector3D), mesh->mTangents);
    }
 
    // texcoords
    char uvFilename[256];
    int numUVs = 0;
    for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; i++)
    {
        if (mesh->mTextureCoords[i]) {
            numUVs++;
            unsigned int numComps = mesh->mNumUVComponents[i];
            aiVector3D* uvw_array = mesh->mTextureCoords[i];
            float* tex = new float[numComps * mesh->mNumVertices];

            for (unsigned int v=0; v < mesh->mNumVertices; v++) {
                if (numComps >= 1) tex[v*numComps+0] = uvw_array[v].x;
                if (numComps >= 2) tex[v*numComps+1] = uvw_array[v].y;
                if (numComps >= 3) tex[v*numComps+2] = uvw_array[v].z;
            }

            sprintf(uvFilename, "%s.uv%d", meshName.c_str(), i);
            writeArray(uvFilename, mesh->mNumVertices, sizeof(float)*numComps, tex);

            delete[] tex;
        }
    }

    // indices
    size_t idxCount = mesh->mNumFaces*3;
    assert(idxCount <= 65536);

    uint16* indices = new uint16[idxCount];
    for (unsigned int i=0; i < mesh->mNumFaces; i++) {
        assert(mesh->mFaces[i].mNumIndices == 3);
        indices[i*3+0] = mesh->mFaces[i].mIndices[0];
        indices[i*3+1] = mesh->mFaces[i].mIndices[1];
        indices[i*3+2] = mesh->mFaces[i].mIndices[2];
    }
    writeArray((meshName + std::string(".idx")).c_str(), idxCount, sizeof(uint16), indices);

    delete[] indices;
}

Tuesday, June 26, 2012

Using the D3DCompiler DLL with older DX9 SDKs via dynamic linking

If you want to compile shaders at runtime without D3DX, you may want to use the D3DCompiler_xx.dll.

Here is an example of how to dynamically load the D3DCompiler_xx.dll under DX9 without requiring a newer DXSDK installation which includes the complete D3D10/11 headers/libraries.

This approach has the added benefit that it works with the mingw compiler / QtCreator as well.

First we declare some required COM interfaces, D3D structures and the D3DCompile() function prototype (ofcourse you can add more functions like D3DCompileFromFile, etc. if needed):

#ifndef __ID3D10Blob_FWD_DEFINED__
#define __ID3D10Blob_FWD_DEFINED__
typedef interface ID3D10Blob ID3D10Blob;
#endif

#define D3DCOMPILER_DLL_A "d3dcompiler_43.dll"

typedef struct {
    LPCSTR Name;
    LPCSTR Definition;
} D3D_SHADER_MACRO;

DEFINE_GUID(IID_ID3D10Blob, 0x8ba5fb08, 0x5195, 0x40e2, 0xac, 0x58,
 0xd, 0x98, 0x9c, 0x3a, 0x1, 0x2);

typedef struct ID3D10BlobVtbl {
    BEGIN_INTERFACE

    HRESULT ( STDMETHODCALLTYPE *QueryInterface )
            (ID3D10Blob * This, REFIID riid, void **ppvObject);

    ULONG   ( STDMETHODCALLTYPE *AddRef )(ID3D10Blob * This);
    ULONG   ( STDMETHODCALLTYPE *Release )(ID3D10Blob * This);
    LPVOID  ( STDMETHODCALLTYPE *GetBufferPointer )(ID3D10Blob * This);
    SIZE_T  ( STDMETHODCALLTYPE *GetBufferSize )(ID3D10Blob * This);

    END_INTERFACE
} ID3D10BlobVtbl;

#define ID3D10Blob_QueryInterface(This,riid,ppvObject) \
    ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) )

#define ID3D10Blob_AddRef(This) \
    ( (This)->lpVtbl -> AddRef(This) )

#define ID3D10Blob_Release(This) \
    ( (This)->lpVtbl -> Release(This) )

#define ID3D10Blob_GetBufferPointer(This) \
    ( (This)->lpVtbl -> GetBufferPointer(This) )

#define ID3D10Blob_GetBufferSize(This) \
    ( (This)->lpVtbl -> GetBufferSize(This) )

interface ID3D10Blob
{
    CONST_VTBL struct ID3D10BlobVtbl *lpVtbl;
};

typedef ID3D10Blob ID3DBlob;

typedef HRESULT (WINAPI *pD3DCompile)
    (LPCVOID                         pSrcData,
     SIZE_T                          SrcDataSize,
     LPCSTR                          pFileName,
     CONST D3D_SHADER_MACRO*         pDefines,
     void*/*ID3DInclude* */          pInclude,
     LPCSTR                          pEntrypoint,
     LPCSTR                          pTarget,
     UINT                            Flags1,
     UINT                            Flags2,
     ID3DBlob**                      ppCode,
     ID3DBlob**                      ppErrorMsgs);
Now we can load the D3DCompiler DLL, import the function via GetProcAddress and then compile our shader:
    HINSTANCE hD3DCompiler = LoadLibraryA(D3DCOMPILER_DLL_A);
    pD3DCompile D3DCompile = NULL;

    if (hD3DCompiler) {
        D3DCompile = (pD3DCompile) GetProcAddress(hD3DCompiler, "D3DCompile");
    }

    ID3DBlob* shaderBlob = NULL;
    ID3DBlob* errorMsg = NULL;
    D3DCompile(vsSource, strlen(vsSource), NULL, NULL, NULL, "main", "vs_2_0",
               0, 0, &shaderBlob, &errorMsg);
    if (errorMsg) {
        char text[1024];
        sprintf(text, "D3DCompile failed:\n%s",
               (char*) ID3D10Blob_GetBufferPointer(errorMsg));
        MessageBoxA(NULL, text, "Error", MB_OK|MB_ICONERROR|MB_TASKMODAL);
        ID3D10Blob_Release(errorMsg);
        exit(-1);
    }

    IDirect3DVertexShader9* shader = NULL;
    if (shaderBlob) {
        gD3DDevice->CreateVertexShader(
           (DWORD *) ID3D10Blob_GetBufferPointer(shaderBlob),
            &shader
        );
        ID3D10Blob_Release(shaderBlob);
    }