自作ラッパの一部をさらしてみる

次の記事が長くなりそうなのと、そろそろソースを載せるたびに細かいコード断片を出すのがアレなので共通ライブラリとして作っている自作のDirectX11のラッパコードから今後掲載する可能性がありそうなコードをさらしてみる。
基本的にペラペラの薄いラッパなのでさらしてもアレなんだけど、「引数を入れてるだけ」的などうでも良いのはともかく、「載せないと分からないよ」的なコードを出してみる。

シェーダ/Blob関連

まずはシェーダソースのコンパイルとかロード関連から。

ID3DBlob *loadShaderBlob(const std::string &filename, const D3D10_SHADER_MACRO *defines, const std::string &shader_type, const std::string &version)
{
    if(isCompiledBlob(filename)){
        return loadCompiledShaderBlob(filename);    // コンパイル済みならロードするだけ
    }else{
        return compileShaderBlob(filename, defines, shader_type, version);  // ソースファイルなのでコンパイルする
    }
}
template <typename FuncType, typename ShaderType>
void createShader(ID3D11Device *device, FuncType CreateXxxShader, ID3DBlob *blob, ShaderType **shader)
{
    if(device == NULL){
        return;
    }
    if(shader == NULL){
        return;
    }
    if(blob == NULL){
        *shader = NULL;
        return;
    }

    (device->*CreateXxxShader)(blob->GetBufferPointer(), blob->GetBufferSize(), NULL, shader);
}
inline ID3D11VertexShader *createVsShader(ID3D11Device *device, ID3DBlob *blob)
{
    ID3D11VertexShader  *shader;

    createShader(device, &ID3D11Device::CreateVertexShader, blob, &shader);

    return shader;
}
// 以下他のシェーダも同様に定義...(「Vertex」の部分を適宜文字列置き換えした形)

これは最上位関数。
呼び出し側はこんな感じになる。

boost::intrusive_ptr<ID3D11Device>                  g_device;
boost::intrusive_ptr<ID3D11ComputeShader>           g_cs;

void shader_sample(void)
{
    const D3D10_SHADER_MACRO        defines[] = {
        {"DEFINE_1", "VAL_1"},
        {"DEFINE_2", "VAL_2"},
        {NULL, NULL}
    };

    boost::intrusive_ptr<ID3DBlob>      blob;    // スコープから抜けるときに自動的に廃棄

    blob = comPtr(loadShaderBlob("source_file.hlsl", defines, "CS", "cs_5_0"));
    g_cs = comPtr(createComputeShader(g_device.get(), blob.get()));
}

boost::intrusive_ptrをうまく動作させるために以下も定義。(共通のヘッダに入れてる)

#include <unknwn.h> // IUnknown

template <class T>
boost::intrusive_ptr<T> comPtr(T *p)
{
    return boost::intrusive_ptr<T>(p, false);   // false指定でAddRef()呼び出しを抑止できる
}

inline void intrusive_ptr_add_ref(IUnknown *p)
{
    p->AddRef();    // コピーしたりすると呼ばれる
}

inline void intrusive_ptr_release(IUnknown *p)
{
    p->Release();   // スコープから抜けるときに呼ばれる
}

DirectXから返ってくるポインタはAddRef()済みなのでintrusive_ptrへ代入するときにAddRefしないように自作の関数経由としている。

で、まだ説明していないblob関連のコード。

bool isCompiledBlob(const std::string &filename)
{
    std::FILE       *fp;
    unsigned int    id = -1;

    fp = std::fopen(filename.c_str(), "rb");
    if(fp == NULL){
        return false;
    }

    std::fread(&id, sizeof(id), 1, fp);     // 最初の4バイトがファイル識別子っぽい

    std::fclose(fp);

    return id == 0x43425844;                // 固定で識別子を確認
}
//------------------------------------------------------------------------------
ID3DBlob *loadCompiledShaderBlob(const std::string &filename)
{
    ID3DBlob    *ret;

    SIZE_T      size;
    std::FILE   *fp;

    fp = std::fopen(filename.c_str(), "rb");
    if(fp == NULL){
        return NULL;
    }

    // ファイルサイズを確認する
    std::fseek(fp, 0, SEEK_END);
    size = std::ftell(fp);
    std::fseek(fp, 0, SEEK_SET);
    size -= std::ftell(fp);

    ::D3D10CreateBlob(size, &ret);

    if(ret != NULL){
        std::fread(ret->GetBufferPointer(), 1, size, fp);
    }

    std::fclose(fp);

    return ret;
}
//------------------------------------------------------------------------------
ID3DBlob *compileShaderBlob(const std::string &filename, const D3D10_SHADER_MACRO *defines, const std::string &shader_type, const std::string &version)
{
    ID3DBlob    *ret;
    ID3DBlob    *err_blob;

    if(defines == NULL){
        return NULL;
    }

    D3DX11CompileFromFile(filename.c_str(), defines, NULL, shader_type.c_str(), version.c_str(), D3D10_SHADER_ENABLE_STRICTNESS|D3D10_SHADER_DEBUG, 0, NULL, &ret, &err_blob, NULL);

    if(err_blob != NULL){
        if(ret == NULL){
            ::MessageBox(NULL, (char *)err_blob->GetBufferPointer(), "Compile Error", MB_OK|MB_ICONEXCLAMATION);
        }

        err_blob->Release();
        err_blob = NULL;
    }

    return ret;
}

とまぁ、特筆すべきことは無いけど。
コンパイルエラー時にMessageBox()してるのはアレなので将来はerr_blobの中身をstd::stringとかに入れて戻すように変更するかも。

サイズ調査関連

テクスチャとかのサイズを調べる関数。
まずは最上位。

inline bool getResolution(ID3D11Resource *target, Resolution &res)
{
    bool                        ret;
    D3D11_RESOURCE_DIMENSION    type;

    target->GetType(&type);

    switch(type){
        case D3D11_RESOURCE_DIMENSION_BUFFER:
            ret = queryAndGetResolution<ID3D11Buffer>(target, res);
        break;
        case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
            ret = queryAndGetResolution<ID3D11Texture1D>(target, res);
        break;
        case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
            ret = queryAndGetResolution<ID3D11Texture2D>(target, res);
        break;
        case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
            ret = queryAndGetResolution<ID3D11Texture3D>(target, res);
        break;
        default:
            ret = false;
        break;
    }

    return ret;
}

switch-caseでテンプレを呼び分けてる。
Resolution構造体は先日の記事で言及した通り幅、高さ、奥行きを持っているだけ。

そして、テンプレの中身はこっち。

template <class ResourceType>
bool queryAndGetResolution(ID3D11Resource *target, Resolution &res)
{
    bool                    ret;
    ResourceType            *tmp;

    target->QueryInterface(__uuidof(ResourceType), reinterpret_cast<void **>(&tmp));
    if(tmp == NULL){
        return false;
    }
    ret = getResolution(tmp, res);
    tmp->Release();

    return ret;
}

で、各個別定義。

inline bool getResolution(ID3D11Buffer *target, Resolution &res)
{
    D3D11_BUFFER_DESC           desc;

    target->GetDesc(&desc);

    // このあたりは実際に呼んだことが無いので自信なし
    res.width_ = (desc.StructureByteStride > 0)? desc.ByteWidth / desc.StructureByteStride : 1;
    res.height_ = 0;
    res.depth_ = 0;

    return true;
}
inline bool getResolution(ID3D11Texture3D *target, Resolution &res)
{
    D3D11_TEXTURE3D_DESC        desc;

    target->GetDesc(&desc);

    res.width_ = desc.Width;
    res.height_ = desc.Height;
    res.depth_ = desc.Depth;

    return true;
}
// 以下1D、2Dも同様...

ということで、getResolution()の第一引数によって多重定義で適当に呼ばれる。

Map()関連

で、最後に無駄に長いのがMap()/Unmap()を自動でやってくれるスマポ的なclass。
既存のスマポにうまく乗っかれないか検討したんだけど、面倒or無理っぽいので自作。

// ElementTypeは要素1個分のデータ型。
// CBufferならCBuffer用の構造体。テクスチャならCOLORREFとかそういうの。
// ResourceTypeはID3D11Buffer、ID3D11Texture1D、ID3D11Texture2D、ID3D11Texture3Dのどれか
template <class ElementType, class ResourceType = ID3D11Resource>
class CpuAccessResource
{
    D3D11_MAPPED_SUBRESOURCE                    info_;          // アドレス計算用
    boost::intrusive_ptr<ResourceType>          target_;        // Map()対象
    boost::intrusive_ptr<ID3D11DeviceContext>   context_;
    Resolution                                  resolution_;    // 解像度

    BYTE *getAddress(UINT x, UINT y, UINT z) const
    {
        // 指定アドレスがresolution_の範囲内かどうかを確認する関数
        if(!isInRange(x, y, z, resolution_)){
            throw PointIsOutofRange(x, y, z, resolution_);  // コーディングミス(まず起こり得ない)の可能性が高いので例外を投げる
        }
        if(info_.pData == NULL){
            return NULL;    // こっちは初期化失敗とかでありえると想定できる(not コーディングミス)ので例外じゃなくてNULLを返す
        }

        // RowPitch、DepthPitchはテクスチャでもバイト単位だったのでこの計算で...
        return static_cast<BYTE *>(info_.pData) + (sizeof(ElementType) * x + info_.RowPitch * y + info_.DepthPitch * z);
    }

public:
    class PointIsOutofRange : public std::out_of_range  // 「指定した座標は範囲外です」例外
    {
        std::string makeCompareResult(UINT val, dot_type range)     // dot_type=unsigned int
        {
            // isInRange()は0<=val<rangeかどうか見てるだけの関数なので掲載は省略
            return str(boost::format("%d < %d %s") % val % range % (isInRange(val, range)? "in range" : "out of range"));
        }
        std::string makeMessage(UINT x, UINT y, UINT z, const Resolution &res)
        {
            return str(boost::format("x is %s, y is %s, z is %s") % makeCompareResult(x, res.width_) % makeCompareResult(y, res.height_) % makeCompareResult(z, res.depth_));
        }
    public:
        PointIsOutofRange(UINT x, UINT y, UINT z, const Resolution &res):std::out_of_range(makeMessage(x, y, z, res)){}
    };
    CpuAccessResource(ID3D11DeviceContext *c, ResourceType *p, D3D11_MAP rw):context_(c), target_(p)
    {
        info_.pData = NULL;

        if((c == NULL) || (p == NULL)){
            return;
        }

        // 後で使うので解像度を調べる
        if(!CommonRoutine::getResolution(target_.get(), resolution_)){
            return;     // 失敗時(コンストラクタ内部なので例外は飛ばさない)
        }

        context_->Map(target_.get(), 0, rw, 0, &info_);
    }
    ~CpuAccessResource()
    {
        if(info_.pData != NULL){
            context_->Unmap(target_.get(), 0);
        }
    }
    ElementType *get3d(UINT x, UINT y, UINT z)
    {
        return reinterpret_cast<ElementType *>(getAddress(x, y, z));
    }
    const ElementType *get3d(UINT x, UINT y, UINT z) const
    {
        return reinterpret_cast<ElementType *>(getAddress(x, y, z));
    }
    ElementType *get(void)
    {
        return get3d(0, 0, 0);
    }
    const ElementType *get(void) const
    {
        return get3d(0, 0, 0);
    }
    ElementType *get2d(UINT x, UINT y)
    {
        return get3d(x, y, 0);
    }
    const ElementType *get2d(UINT x, UINT y) const
    {
        return get3d(x, y, 0);
    }

    static CpuAccessResource<ElementType, ID3D11Buffer> getOverwritableBuffer(ID3D11DeviceContext *c, ID3D11Buffer *b)
    {
        return CpuAccessResource<ElementType, ID3D11Buffer>(c, b, D3D11_MAP_WRITE_DISCARD);
    }
    static const CpuAccessResource<ElementType, ID3D11Resource> getReadableTexture(ID3D11DeviceContext *c, ID3D11Resource *t)
    {
        return CpuAccessResource<ElementType, ID3D11Resource>(c, t, D3D11_MAP_READ);
    }
};

使うときはこんな感じで。

boost::intrusive_ptr<ID3D11DeviceContext>       g_con;
boost::intrusive_ptr<ID3D11Texture2D>           g_tex;
boost::intrusive_ptr<ID3D11Buffer>              g_cbuf;

struct VertexInputType
{
    D3DXMATRIX  view_projection_;
    D3DXMATRIX  world_;
};

void vertex_input(const CameraType &camera, const D3DXMATRIX pos)
{
    VertexInputType     *ptr;

    // この後Map()される
    ptr = CpuAccessResource<VertexInputType>::getOverwritableBuffer(g_con.get(), g_cbuf.get()).get();
    if(ptr == NULL){
        return;
        // ここでUnmap()される
    }

    ptr->view_projection_ = camera.getViewProjection();
    ptr->world_ = pos;

    // ここでUnmap()される
}

COLORREF getPixel(int x, int y, int z)
{
    COLORREF        *ptr;

    // この後Map()される
    ptr = CpuAccessResource<COLORREF>::getReadableTexture(g_con.get(), g_tex.get()).get3d(x, y, z);
    if(ptr == NULL){
        throw std::runtime_error("null po.");
        // ここでUnmap()される
    }

    return *ptr;
    // ここでUnmap()される
}

[2016/05/26 21:45 追記]
いまさらだけど、RAIIの書き方を間違えていることに気づいた。
一時変数にしちゃだめだった。

CpuAccessResource<COLORREF> raii = CpuAccessResource<COLORREF>::getReadableTexture(g_con.get(), g_tex.get());
ptr = raii.get3d(x, y , z);

みたいにいったんローカル変数として作らないとすぐ一時オブジェクトが破棄されてしまっていた。
[追記終了]


という感じで、今のところはこんな感じ。
これで今後コードを載せるときに詳細をスキップできるかな。