🗝️

DirectXでもOpenGLライクなID管理

2022/09/01に公開約3,400字

はじめに

一般的なプログラムは時代を追うごとに抽象化が進みますが、グラフィックスAPIはパフォーマンスという特性上から、時代を追うごとに低レイヤーな部分のプログラミングが要求されるようになっていると思います。

私が始めて触ったグラフィックスAPIはDirectx11です。学校の授業でOpenGLに触れる機会があったのですが、両者のリソース生成の違いに違和感を感じました。なので、OpenGL内部でどん仕組みになっているのかという想像と、DirectX11でも同じようなことをしてみようと思います。

両者の違いの例(バッファ生成)

ID3D11Device::CreateBuffer(
    const D3D11_BUFFER_DESC*      pDesc,
    const D3D11_SUBRESOURCE_DATA* pInitialData,
    ID3D11Buffer**                ppBuffer);

DirectX11の場合はバッファーの仕様bufferDescと、バッファーの格納先ポインターbufferを渡します。バッファを生成するだけなので、pInitialDataはnullptrでOK

glCreateBuffers(int size, int* buffer);

OpenGLはめっちゃ簡単ですが、生成数sizeと謎のパラメータbufferが要求されます。

想像のお話

DirectX11はOpenGLと比べて少し書くことが多いですが、バッファへのポインター型はなにか?バッファの使用法はなにか?などを自分で指定しているので、わかりやすいと思います。しかし、OpenGLはわかりにくいです。上でも言いましたが、OpenGLの場合はサイズとint型のポインターが求められます。DirectX11のAPIと比較して考えてみると、バッファへのポインターと予想できます。

int buffer1 buffer2;
glCreateBuffers(1, &buffer1); // buffer1は'0'
glCreateBuffers(1, &buffer2); // buffer2は'1'

実際に確認してみるとbufferの値は'0'でした。また、バッファ複数作ってみると0, 1...とポインター先の変数の値が1ずつ増えます。どうやらint型で連番の識別子のようです。

glBindBuffer(GL_ARRAY_BUFFER, buffer);

glCreateBuffersで生成したバッファーをbuffer識別子で管理し、描画するバッファーをバインドする際にglBindBufferbufferを指定しています。

この識別子の生成と管理を自前で用意すれば、DirectX11でも、OpenGL形式のインターフェースが用意できそうです。

DirectX11で同様に

識別子の生成と管理は単純にstd::vetor<ResourceType>を使うことで実装できます。CreateBuffer関数内部のID3D11Device::CreateBufferでバッファを確保し、バッファ配列にpush_back関数でバッファを追加します。識別番号は連番なので、size関数で配列のサイズを返すことで識別子を取得できます。eraseclearで要素数を変更しない限り、識別子は機能します。

std::vector<ID3D11Buffer*> buffers;

int D3DCreateBuffer()
{
    ID3D11Buffer* buf;
    device->CreateBuffer(bufferDesc, nullptr, buf);
    
    int id = buffers.size();  // 要素数(ID)を取得
    buffers.push_back(buf); // リストに追加
    
    return id;
}

void D3DBindBuffer(int bufferID)
{
    context->IASetVertexBuffer(0, 1, buffers[bufferID], 0, 0);
}
// バッファを生成しつつ識別子を取得
int id = D3DCreateBuffer();

// 識別子をもとにバインドするバッファを指定
D3DBindBuffer(id);

抽象化

ここからはおまけ...
呼び出しを共通化できたら、レンダラークラスので共通呼び出しができるインターフェースクラスを作ればもっと便利になる。

class IRenderAPI
{
    virtual void CreateBuffer(int* id) = 0;
}

class D3DRenderer : IRenderAPI
{
    void CreateBuffer(int* id) override
    {
        ID3D11Buffer* buf;
	device->CreateBuffer(bufferDesc, nullptr, buf);
    
        id = buffers.size();  // 要素数(ID)を取得
        buffers.push_back(buf); // リストに追加
    }
}

class OpenGLRenderer public : IRenderAPI
{
    void CreateBuffer(int* id) override
    {
        glCreateBuffers(1, id);
    }
}

DirectXでも識別子を利用してOpenGLライクなインターフェースにすることが出来た。
さらにIRenderAPIのようなインターフェースを用意すれば、共通呼び出しで異なるレンダラーのAPIを呼び分けることが出来るようになる。


enum class ERendererType
{
    DirectX = 0,
    OpenGL  = 1,
}

class Renderer
{
    static void Init(ERenderType renderer)
    {
        switch (renderer)
	{
	    case DirectX: 
	        m_RenderAPI = new D3DRenderer();    
	        break;
		
	    case OprnGL : 
	        m_RenderAPI = new OpenGLRenderer(); 
		break;
	}
	
	m_RenderAPI->Init();
    }
    
    static void CreateBuffer(int* id)
    {
        m_RenderAPI->CreateBuffer(id);
    }
    
    static IRenderAPI* m_RenderAPI  = nullptr;
    static ERenderType m_RenderType = ERenderType::DirectX;
}

あとは、Rendererの初期化時にレンダラーの種類ERenderTypeを指定するだけでOK

まとめ

DirectX11でもリソース配列の要素数をID(識別子)として扱うことでOpenGLのようなID指定でリソース管理をすることが出来るようになった。また、両者の引数をさらに統一することで同じインターフェースで2つのレンダラーAPIを呼び分けることも出来るようになる。

Discussion

ログインするとコメントできます