Adventures with Porting Code to Visual Studio 2015 and No DirectX SDK

# Adventures with Porting Code to Visual Studio 2015 and No DirectX SDK

22:40
Sat
08
Aug 2015

I just installed new Visual Studio 2015 Community Edition and ported my personal project to it. At the same time, I uninstalled old DirectX SDK Jun 2010 and started using new DirectX headers bundled with Windows SDK, which is installed together with Visual Studio. These two transitions are not connected - I could already switch to new DX headers years ago, but I decided to do it now. While transition to VS 2015 was smooth, abandoning DirectX SDK caused some problems, because new DirectX doesn't contain D3DX library. Here is a dump of the problems and solutions that I encountered during this effort:

1. I uninstalled DirectX SDK Jun 2010, Visual Studio 2013 Community Edition and all other components that seemed related to it, like Microsoft SQL. I left all "Microsoft Visual C++ XX Redistributable" though, because these are required by many applications and intended to be installed on target machine, not necessarily as a part of development environment.

Next, I downloaded and installed new Visual Studio 2015 Community Edition. During this long process, I was thinking what should I expect from the new IDE... Whether Microsoft did a good job this time? On one hand, it is suprising that C++ is now an optional installation component, so it seems like native code is in shadow comparing to all these new and trendy cloud/web/mobile/managed technologies. On the other hand, table: C++11/14/17 Features In VS 2015 RTM shows that the new C++ compiler caught up with many features of new C++11/14/17 language, which gives hope that authors still treat native code development seriously.

2. The looks of new IDE is so similar to the previous version it is hard to notice any differences. After launching it, I had to first recompile static libraries that my project depends on. That was zlib 1.2.8 and my CommonLib. Converting project to new version, as well as the build itself went smoothly, without any problems - which is unusual with C/C++ libraries :) Just as in previous version, headers and libs of standard C library, standard C++ library (STL) and WinAPI are already bundled with the Visual Studio, so there is no need to install or configure anything additional before you can use them.

3. Here comes the real issue: headers and libs of DirectX 9/10/11/12 are now also bundled with Visual Studio, but they don't contain D3DX library and some other deprecated DirectX libraries. I knew about that and that's why I postponed the transition so long. Now I had to finally learn what to replace these functions with. The best article about leaving D3DX behind is probably: Living without D3DX. Fortunately I didn't use many of D3DX features, like effects, sprites, fonts or math functions, because I have my own. I did use three things though: error reporting, texture loading and shader compilation.

3.1. For obtaining error message from DirectX error code of type HRESULT I used header <DxErr.h>, library dxerr.lib and functions: DXGetErrorString and DXGetErrorDescription. They no longer exist in new Windows SDK. A standard FormatMessage function should be used instead, which also returns message for WinAPI error codes obtained from GetLastError. I used this function before for WinAPI errors, so for me it was following code change in a constructor of my DirectX exception class:

DirectXError::DirectXError(HRESULT hr, const tstring &Msg, const tstring &File, int Line)
{
    Push(
        _T("(DirectXError,0x") +
        common::UintToStrR<uint32_t>((uint32_t)hr, 16) +
        _T(") ") +
        tstring(DXGetErrorString(hr)) +
        _T(" (") +
        tstring(DXGetErrorDescription(hr)) +
        _T(")") );
    Push(Msg, File, Line);
}

-->

DirectXError::DirectXError(HRESULT hr, const tstring &Msg, const tstring &File, int Line)
{
    tstring s =
        _T("(DirectXError,0x") +
        common::UintToStrR<uint32>((uint32_t)hr, 16) +
        _T(") ");
    tchar *msg = 0;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, // dwFlags
        0, // lpSource
        (DWORD)hr, // dwMessageId
        0, // dwLanguageId
        (tchar*)&msg, // lpBuffer (Intentional cast from tchar** to tchar*!)
        0, // nSize
        0); // Arguments
    Push(s + msg);
    LocalFree(msg);
    Push(Msg, File, Line);
}

3.2. I no longer have D3DX11CreateTextureFromFile function available that I used to load textures in various image formats, like JPG, PNG, TGA etc. By reading some web pages I found that there are multiple options to replace it.

Of course the recommended solution is to prepare all textures, shaders and other assets offline in some custom binary format that can be loaded directly into DirectX, but I don't want to do that currently.

I decided to use DirectXTex (GitHub, Documentation) - an open-source, MIT-licensed Microsoft library that provides texture operations. I logged in to GitHub, downloaded the repository and compiled its DirectXTex project in my Visual Studio 2015 as a static library without any problems. I then copied LIB files, as well as public headers (DirectXTex.h, DirectXTex.inl) into a special directory and included it in my project.

This library is able to load textures from files in various formats, but it's not so easy as a single function call. First, there are actually 3 different code paths for loading images. Most of the formats (including JPG, PNG, BMP, TIF, GIF, ICO) is handled by function LoadFromWICFile, which uses WIC - Windows Imaging Component, a part of WinAPI. But it cannot handle all the formats popular among game developers, so there is separate function that handles TGA files: LoadFromTGAFile and another one that handles DirectX native format DDS: LoadFromDDSFile. Only these three functions cover all the formats that were handled by the old D3DX.

Second, the loading is done to a CPU memory contained in data structures like class ScratchImage, struct Image or TexMetadata. It must be then transferred to GPU to create a DirectX texture by separate call to function CreateTexture.

I came up with a simple solution of implementing my own version of nonexistent function D3DX11CreateTextureFromFile in following way:

typedef struct ID3DX11ThreadPump ID3DX11ThreadPump;
typedef struct D3DX11_IMAGE_LOAD_INFO D3DX11_IMAGE_LOAD_INFO;

HRESULT D3DX11CreateTextureFromFile(
    _In_  ID3D11Device           *pDevice,
    _In_  LPCTSTR                pSrcFile,
    _In_  D3DX11_IMAGE_LOAD_INFO *pLoadInfo,
    _In_  ID3DX11ThreadPump      *pPump,
    _Out_ ID3D11Resource         **ppTexture,
    _Out_ HRESULT                *pHResult)
{
    assert(pLoadInfo == nullptr);
    assert(pPump == nullptr);

    DirectX::ScratchImage image;
    HRESULT hr = S_OK;
    if (common::StrEnds(pSrcFile, L".dds", false))
        hr = DirectX::LoadFromDDSFile(pSrcFile, DirectX::DDS_FLAGS_NONE, nullptr, image);
    else if (common::StrEnds(pSrcFile, L".tga", false))
        hr = DirectX::LoadFromTGAFile(pSrcFile, nullptr, image);
    else
        hr = DirectX::LoadFromWICFile(pSrcFile, DirectX::WIC_FLAGS_NONE, nullptr, image);
    if (FAILED(hr))
        return hr;

    hr = DirectX::CreateTextureEx(
        pDevice,
        image.GetImages(),
        image.GetImageCount(),
        image.GetMetadata(),
        D3D11_USAGE_IMMUTABLE,
        D3D11_BIND_SHADER_RESOURCE,
        0, // cpuAccessFlags
        0, // miscFlags
        false, // forceSRGB
        ppTexture);
    return hr;
}

It worked perfectly, except this code doesn't create mipmaps, so after some more coding, I ended up with this:

CTexture::CTexture(
    const wchar_t* filePath,
    bool generateMipMaps,
    const D3D11_SHADER_RESOURCE_VIEW_DESC* shaderResourceViewDesc) :
    m_FilePath(filePath)
{
    ERR_TRY

    ZeroMemory(&m_Desc, sizeof m_Desc);

    wprintf(L"Loading texture \"%s\"\n", filePath);

    assert(g_App && g_App->Dev());
    ID3D11Device* dev = g_App->Dev();

    ID3D11Resource *resource;
    DirectX::ScratchImage image;

    HRESULT hr = S_OK;
    if (common::StrEnds(filePath, L".dds", false))
    {
        ERR_GUARD_DIRECTX( DirectX::LoadFromDDSFile(filePath, DirectX::DDS_FLAGS_NONE, nullptr, image) );
    }
    else if (common::StrEnds(filePath, L".tga", false))
    {
        ERR_GUARD_DIRECTX( DirectX::LoadFromTGAFile(filePath, nullptr, image) );
    }
    else
    {
        ERR_GUARD_DIRECTX( DirectX::LoadFromWICFile(filePath, DirectX::WIC_FLAGS_NONE, nullptr, image) );
    }

    DirectX::ScratchImage imageWithMipMaps;
    DirectX::ScratchImage* finalImage = &image;
    if(generateMipMaps && image.GetMetadata().mipLevels == 1)
    {
        const DirectX::Image* origImg = image.GetImage(0, 0, 0);
        assert(origImg);
        ERR_GUARD_DIRECTX( DirectX::GenerateMipMaps(
            *origImg,
            0, // filter
            0, // levels
            imageWithMipMaps) );
        finalImage = &imageWithMipMaps;
    }

    ERR_GUARD_DIRECTX( DirectX::CreateTextureEx(
        dev,
        finalImage->GetImages(),
        finalImage->GetImageCount(),
        finalImage->GetMetadata(),
        D3D11_USAGE_IMMUTABLE,
        D3D11_BIND_SHADER_RESOURCE,
        0, // cpuAccessFlags
        0, // miscFlags
        false, // forceSRGB
        &resource) );

    D3D11_RESOURCE_DIMENSION resDim;
    resource->GetType(&resDim);
    if(resDim != D3D11_RESOURCE_DIMENSION_TEXTURE2D)
    {
        resource->Release();
        THROW_ERR(Format_r(L"\"%s\" is not 2D texture.", filePath));
    }

    m_Texture.reset((ID3D11Texture2D*)resource);

    D3D11_SHADER_RESOURCE_VIEW_DESC defaultSRVDesc;
    if(!shaderResourceViewDesc)
    {
        defaultSRVDesc.Format = DXGI_FORMAT_UNKNOWN;
        defaultSRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
        defaultSRVDesc.Texture2D.MostDetailedMip = 0;
        defaultSRVDesc.Texture2D.MipLevels = UINT_MAX;

        shaderResourceViewDesc = &defaultSRVDesc;
    }

    ID3D11ShaderResourceView* shaderResourceView;
    ERR_GUARD_DIRECTX( dev->CreateShaderResourceView(m_Texture.get(), shaderResourceViewDesc, &shaderResourceView) );
    m_ShaderResourceView.reset(shaderResourceView);

    ERR_CATCH( Format_r(L"Cannot load texture \"%s\".", filePath) )
}

DirectXTex library can do much more than that. It turns out it can:

so it's very useful library. The code comes with some other projects as well, like DDSTextureLoader (a lightweight loader for DDS format only), DDSView, Texconv and Texassemble (console tools).

There is another open-source Microsoft library like this: DirectXTK (GitHub, Documentation), but it serves slightly different purpose - next to texture loading, it also provides some facilities for sprites, effects, models, fonts and other useful stuff for DX11. For sure it would be interesting to learn more about this library as well.

3.3. Ideally shaders should also be compiled to binary form offline by fxc.exe program (now shipped with Visual Studio as well), but fortunately the HLSL compiler in form of a library isn't gone, it just changed its name. Now without D3DX, it is a separate library called D3DCompile. It requires including <D3Dcompiler.h>, linking with d3dcompiler.lib and shipping your code with d3dcompiler_47.dll file. (Yes, that's what Microsoft recommends - shipping the DLL with your program, not requiring installation of another package, like Visual C++ Redistributable!) Functions changed their names, but they take pretty much the same parameters. Only pPump and pHResult are gone, which I didn't use anyway. So in my case, the change looked like this:

HRESULT hr = D3DX11CompileFromFile(
    filePath, // pSrcFile
    nullptr, // pDefines
    nullptr, // pInclude
    functionName, // pFunctionName
    profile, // pProfile
    0, // Flags1
    0, // Flags2
    nullptr, // pPump
    &shaderBlob, // ppShader
    &errorBlob, // ppErrorMsgs
    NULL); // pHResult

-->

HRESULT hr = D3DCompileFromFile(
    filePath, // pFileName
    nullptr, // pDefines
    nullptr, // pInclude
    functionName, // pEntrypoint
    profile, // pTarget
    0, // Flags1
    0, // Flags2
    &shaderBlob, // ppCode
    &errorBlob); // ppErrorMsgs

4. Finally, I noticed some new warnings when building my projects. I use /W3 warning level and try to keep my code clean from any warnings, so I was glad to see that new Visual found some issues in my code, like:

visualscene.cpp(200): warning C4476: 'wprintf' : unknown type field character 'M' in format specifier
visualscene.cpp(200): warning C4474: 'wprintf' : too many arguments passed for format string
wprintf(L"%MaskEnable = true\n", GetIndent(indent + 1));
-->
wprintf(L"%sMaskEnable = true\n", GetIndent(indent + 1));

I don't like this one, but I made the change just to silence the warning:

engine.cpp(566): warning C4838: conversion from 'int' to 'uint' requires a narrowing conversion
uint macroValues[] = {
    constantAmbientEnabled      ? 1 : 0,
    hemisphereAmbientEnabled    ? 1 : 0,
    directionalLightEnabled     ? 1 : 0,
    directionalLightDualEnabled ? 1 : 0,
};

-->

uint macroValues[] = {
    constantAmbientEnabled      ? 1u : 0u,
    hemisphereAmbientEnabled    ? 1u : 0u,
    directionalLightEnabled     ? 1u : 0u,
    directionalLightDualEnabled ? 1u : 0u,
};

As a conclusion, I would say that the new Visual Studio 2015 Community Edition is good and worth upgrading. IDE looks similar to 2013 version. I also didn't notice any slowdown comparing to the old one. C++ compiler is improved, with new warnings, more modern C++ features and certainly many other improvements and bug fixes.

Transition from 5-year-old DirectX SDK to new DirectX headers included in Windows SDK is not that simple, depending on how many features of D3DX do you use, but in my case it was easier than I expected. It may seem that Microsoft makes our lives harder by breaking backward compatibility, but at same time they provide nice replacements in form of open-source libraries and encourage transition to better practices, like offline asset preparation.

Comments | #visual studio #directx #c++ Share

Comments

STAT NO AD
[Stat] [STAT NO AD] [Download] [Dropbox] [pub] [Mirror]
Copyright © 2004-2017