1. Qiita
  2. 投稿
  3. ライブラリ

Direct3D 12 & Vulkan ラッパーライブラリ 「a3d」

  • 23
    いいね
  • 0
    コメント

この記事は,「ゲームエンジン・ライブラリ・ツールの開発 Advent Calendar」の16日目の記事です。

皆さん,こんにちわ。Pocolです。

D3D12&Vulkanラッパーライブラリ「a3d」
…というものを作ってみました。

Githubでソースコードを公開中です。
https://github.com/ProjectAsura/asura-SDK/tree/master/a3d
a3dライブラリのライセンスは MITライセンスです。

ゲームアプリケーションを作成する場合,ゲームエンジンなどで用意されているAPIを使用するか,DirectXやOpenGLのようにプラットフォームごとに用意されているグラフィックスAPIを直接叩いて作成する方法が考えれます。A3Dは,ゲームエンジンのように大きなライブラリではなく,プラットフォーム毎のグラフィックスAPIを共通で使えることを目的とした抽象化グラフィックスAPIです。

a3d_abstraction.png

OpenGLにも対応できると思いますが,対応が大変なのでやっていません。
また,NDAが関係するものはa3dライブラリでは現在対応外としています。要望があれば対応自体は可能だと思います。

今回はこのa3dライブラリの実装についての説明と,実際のライブラリの使い方について
ちろっと紹介したいと思います。

開発のきっかけ

「自分だったら抽象化ライブラリをどう作るか?」
そう思って,実装を開始して出来上がったのがa3dライブラリです。
なんでそう思ったのかについては,聞かないでください。ここ半年の仕事で色々と苦しんで,色々とあったんです…。

実装について

実装期間は1日多くて2時間ぐらいで,2か月ぐらいで基本実装部分を作りました。
現在は,実装検証しています。使いづらい場所を直したり,バグが無いか確かめたり,デバッグ作業しています。
まだ,開発中です。
バグは多々あると思いますが,それっぽく動くようになってきたので今回公開することにしてみました。

a3dは,本業での抽象化ライブラリ対応作業の実装経験から以下のことに気を付けました。
・むやみやたりにインタフェース・関数を作りまくらない。
・薄くラップする。
・公開ヘッダは1ファイルのみ。ネイティブ依存はしないようにする
・内部実装はある程度好き勝手にしやすいようにする。
・プラットフォームごとにファイル・ソリューション・プロジェクトを分ける。
・D3D12世代に適合しやすい設計にする。
・どうしようもない時のために逃げる手段を用意しておく。

まず,最初の「むやみやたらにインタフェース・関数を作りまくらない」についてですが,これは単純に数が多くなると実装する時間がかかるので,可能な限りそうならないようにした方が良いです。あと,実装する箇所が多いとそれだけバグが生まれる可能性も高くなるので,メンテナンスも考えるとコンパクトであることに越したことはないです。

つづいて,「薄くラップする」についてですが,ゲーム用途を考えると高速である方が望ましいです。そのため,できる限りネイティブに近くなるように,抽象化層の実装を薄くしておいた方がパフォーマンスが良くなるからです。あと,内部であれこれやるとバグが生まれるし,ほかの人にデバッグしてもらったりする場合も,内部実装の説明が少なくて済みます。

次の公開ヘッダは1ファイルのみは,移植をしやすくしたり,不毛なビルドエラーと格闘しないようにするためです。なんかこのPCだけヘッダなくてビルドできないとか,そういう無駄な時間をできる限り少なくするためです。

「プラットフォームごとにファイル・ソリューション・プロジェクトを分ける」についてですが,これは本業の方で,そうやらずにエンジンを作っているプロジェクトがあり,その結果内部が#if-#endifのオンパレードで,コードの可読性が悪く,メンテナンスコストが高すぎて炎上しているのを間近で見たからです。1プラットフォーム直すと全プラットフォーム直さないといけないとか,「滅茶苦茶大変そうだな…」と傍から見ていても思います。ここは”他人の振り見て我が振り直せ”です。他人がわざわざ踏んでくれた地雷をまた自分で踏まなくても良いでしょう。こうした他プロジェクトの失敗談は積極的に聞き,問題を起こさないように対策をすべきかなと思います。

「D3D12世代に適合しやすい設計にする。」これは単なる趣味ですね。それ以上でも以下でもない。

「どうしようもない時のために逃げる手段を用意しておく」ですが,a3dライブラリでは,どうしようもないときのために中間コマンドを用意してあります。D3D12とVulkanの実装では基本的に使いませんが,うまく実装できなくなった場合は,中間コマンドを利用して最後にコマンドの依存関係を解決できるようにするという最終手段を用意しています。ただ用意しても,「いざ使ってみたらだめだった」みたいになると困るので,実装検証のためにD3D11版で中間コマンドを使ってどうにかできるかどうかを実装をしてみました。一応それっぽく動いてくれています。

ちなみに気を付けているだけで,上記であげた項目がちゃんと解決できているかどうかは定かではありません…。

フォルダ構成

フォルダ構成は下記のような感じです。
a3d_folder.png
includeフォルダにa3d.hという名前のヘッダーファイルが1つだけあります。
srcフォルダ下にこのa3d.hで定義されているインタフェースを実装するためのソースコードを配置しています。srcフォルダは,プラットフォーム依存の箇所はd3d12やd3d11,vulkanといったぐらいに分けてあり,プラットフォームをまたいで実装できそうなもののうちカテゴリ分けできるものは,allocatorのようなにフォルダをきって配置しています。
projectフォルダにはVisual Studioのソリューションファイルとプロジェクトファイルをそれぞれプラットフォームごとに分けています。
現在は,Visual Studio 2015しか対応していません。Visual Studio 2017 RCは対応中で,手元ではVS2015と同じように動いています。こちらは,もう少し様子見をしてからアップしようと思います。

インタフェース

a3dのAPIはd3d12に出来るところはd3d12風に,そのようにできない箇所は自分で使いやすい風に決めています。
APIの仕様はVulkanに寄せないとどうしようもないということが本業の方で分かったので,インタフェースの名前や役割はVulkanに近い感じになっています。
基本的にオブジェクトはd3dのように参照カウンタ方式で管理する形式になっています。

///////////////////////////////////////////////////////////////////////////////////////////////////
// IReference interface
//! @brief      参照カウンタインタフェースです.
///////////////////////////////////////////////////////////////////////////////////////////////////
struct A3D_API IReference
{
    //---------------------------------------------------------------------------------------------
    //! @brief      デストラクタです.
    //---------------------------------------------------------------------------------------------
    virtual A3D_APIENTRY ~IReference()
    { /* DO_NOTHING */ }

    //---------------------------------------------------------------------------------------------
    //! @brief      参照カウントを増やします.
    //---------------------------------------------------------------------------------------------
    virtual void A3D_APIENTRY AddRef() = 0;

    //---------------------------------------------------------------------------------------------
    //! @brief      参照カウントを減らし,カウントがゼロになったら解放します.
    //---------------------------------------------------------------------------------------------
    virtual void A3D_APIENTRY Release() = 0;

    //---------------------------------------------------------------------------------------------
    //! @brief      参照カウントを取得します.
    //!
    //! @return     参照カウントを返却します.
    //---------------------------------------------------------------------------------------------
    virtual uint32_t A3D_APIENTRY GetCount() const = 0;
};

実装側は,出来るだけOS依存にしたくないので,std::atomicを使って実装する感じになっています。

// 実装例
struct A3D_API IHogehoge : IReference
{
    virtual A3D_APIENTRY ~IHogehoge()
    { /* DO_NOTHING */ }
};

class A3D_API Hogehoge : IHogehoge, BaseAllocator
{
public:
    void A3D_APIENTRY AddRef() override;
    void A3D_APIENTRY Release() override;
    uint32_t A3D_APIENTRY GetCount() const override;

private:
   std::atomic<uint32_t>  m_RefCount;

   HogeHoge();
   virtual ~Hogehoge();
};

Hogehoge::Hogehoge()
: m_RefCount(1)
{ /* DO_NOTHING */ }

Hogehoge::~Hogehoge()
{ /* DO_NOTHING */ }

void Hogehoge::AddRef() 
{ m_RefCount++; }

void Hogehoge::Release()
{
    m_RefCount--;
    if (m_RefCount == 0)
    { delete this; }
}

uint32_t Hogehoge::GetCount() const
{ return m_RefCount; }

デバイスインタフェースを介して作成されるオブジェクトは,終了処理でデバイスインタフェースが必要になることがあるので,各オブジェクトでデバイスインタフェースをAddRef()して保持するようになっています。
実装処理の最中にデバイスインタフェースが必要になることは多々あるので,IDeviceChildというインタフェースを作成して,各オブジェクトで保持しているデバイスインタフェースを外部から取れるようにしています。このあたりはd3dと同じ感じです。こうしたものがないと,グローバル関数的なものやシングルトンなものでデバイスインタフェースを取ってくる仕組みが別途必要になる気がします。

///////////////////////////////////////////////////////////////////////////////////////////////////
// IDeviceChild interface
//! @brief      デバイスチャイルドインタフェースです.
///////////////////////////////////////////////////////////////////////////////////////////////////
struct A3D_API IDeviceChild : IReference
{
    //---------------------------------------------------------------------------------------------
    //! @brief      デストラクタです.
    //---------------------------------------------------------------------------------------------
    virtual A3D_APIENTRY ~IDeviceChild()
    { /* DO_NOTHING */ }

    //---------------------------------------------------------------------------------------------
    //! @brief      デバイスを取得します.
    //!
    //! @param[out]     ppDevice        デバイスの格納先です.
    //---------------------------------------------------------------------------------------------
    virtual void A3D_APIENTRY GetDevice(IDevice** ppDevice) = 0;
};

オブジェクトの対応関係

各グラフィックスAPIのオブジェクトとa3dライブラリのオブジェクトの対応関係は次のような感じになっています。

a3d Direct3D 12 Direct3D 11 Vulkan
IDevice ID3D12Device ID3D12DescriptorHeap ID3D11Device ID3D11DeviceContext vkDevice
IBuffer ID3D12Resource ID3D11Buffer vkBuffer vkDeviceMemory
IBufferView D3D12_CPU_DESCRIPTOR_HANDLE D3D12_GPU_DESCRIPTOR_HANDLE ID3D11ShaderResourceView ID3D11UnorderedAccessView 対応無し
ICommandList ID3D12CommandAllocator ID3D12GraphicsCommandList 対応無し (エミュレーション) vkCommandPool vkCommandBuffer
ICommandSet ID3D12CommandSignature 対応無し (エミュレーション) 対応無し (エミュレーション)
IDescriptorSet D3D12_GPU_DESCRIPTOR_HANDLE 対応無し (エミュレーション) vkDesctiptorSet
IDescriptorSetLayout ID3D12RootSignature 対応無し (エミュレーション) vkDescriptorSetLayout vkPipelineLayout vkDescriptorPool
IFence ID3D12Fence ID3D11Query vkFence
IFrameBuffer D3D12_CPU_DESCRIPTOR_HANDLE ID3D11RenderTargetView ID3D11DepthStencilView vkFramebuffer vkRenderPass
IPipelineState ID3D12PipelineState ID3D11VertexShader ID3D11DomainShader ID3D11GeometryShader ID3D11HullShader ID3D11PixelShader ID3D11ComputeShader ID3D11RasterizerState ID3D11BlendState ID3D11DepthStencilState ID3D11InputLayout vkPipeline vkPipelineCache
IQueryPool ID3D12QueryHeap ID3D11Query vkQueryPool
IQueue ID3D12CommandQueue 対応無し (エミュレーション) vkQueue
ISampler D3D12_GPU_DESCRIPTOR_HANDLE ID3D11SamplerState vkSampler
ISwapChain IDXGISwapChain4 IDXGISwapChain2 vkSurfaceKHR vkSwapChainKHR
ITexture ID3D12Resource ID3D11Resource vkImage vkDeviceMemory
ITextureView D3D12_CPU_DESCRIPTOR_HANDLE D3D12_GPU_DESCRIPTOR_HANDLE ID3D11ShaderResourceView ID3D11RenderTargetView ID3D11DepthStencilView ID3D11UnorderedAccessView vkImageView

ざっくりですがオブジェクトについて触れておきます。

  • IDevice
    デバイスインタフェースです。オブジェクトの生成などを行います。

    • D3D12実装では,IDXGIFactory1, ID3D12Device, ID3D12DesctiptorHeap, ID3D12CommandQueueをそれぞれ保持します。
    • Vulkan実装では,VkInstance, VkDevice, VkPhysicalDevice, VkQueueをそれぞれ保持します。
  • IBuffer
    バッファインタフェースです。頂点バッファやインデックスバッファ,定数バッファとして利用するためのメモリを持つインタフェースになります。

    • D3D12実装では,ID3D12Resource,D3D12_CPU_DESCRIPTOR_HANDLE, D3D12_GPU_DESCRIPTOR_HANDLEをそれぞれ保持します。
    • Vulkan実装では,VkBuffer, VkDeviceMemoryをそれぞれ保持します。
  • IBufferView
    バッファビューインタフェースです。現状は,定数バッファビューとして使用するために作成するインタフェースとなっています。

    • D3D12実装も,Vulkan実装もBufferクラス(IBufferインタフェースの具象化クラス)へのポインタを持ちます。
  • ICommandList
    コマンドバッファを抽象化したコマンドリストインタフェースです。

    • D3D12実装では,ID3D12CommandAllocator, ID3D12GraphcisCommandListをそれぞれ保持します。
    • Vulkan実装では,VkCommandPool, VkCommandBufferをそれぞれ保持します。
  • ICommandSet
    コマンドをまとめて持つインタフェースです。主にインダイレクトコマンドを発行するために使用します。

    • D3D12実装では,ID3D12CommandSignatureを保持します。
    • Vulkan実装では,対応するオブジェクトが無いため,インダイレクトコマンド実行のため引数データを保持します。今後,VK_NVX_device_generated_commands拡張が使える場合には,拡張機能を使用するように変更予定です。
  • IDescriptorSetLayout
    シェーダで利用するディスクリプタのレイアウトを定義をするインタフェースです。

    • D3D12実装では,ID3D12RootSignatureを保持します。
    • Vulkan実装では,VkDescriptorSetLayout, VkPipelineLayout, VkDescriptorPoolをそれぞれ保持します。
  • IDescriptorSet
    テクスチャや定数バッファといったシェーダで利用するディスクリプタのまとまりを持つインタフェースです。このオブジェクトは,ディスクリプタセットレイアウトからアロケートすることにより生成されます。

    • D3D12実装では,D3D12_GPU_DESCRIPTOR_HANDLEの配列を保持します。
    • Vulkan実装では,VkDescriptorSetとVkWriteDescriptorSetをそれぞれ保持します。
  • IFence
    コマンドの同期をとるためのインタフェースです。

    • D3D12実装では,ID3D12Fence, イベントハンドルをそれぞれ保持します。
    • Vulkan実装では,VkFenceを保持します。
  • IFrameBuffer
    カラーバッファと深度バッファを保持するインタフェースです。

    • D3D12実装では,D3D12_CPU_DESCRIPTOR_HANDLEを保持します。わかりやすく言うとレンダーターゲットビューと深度ステンシルビューを持ちます。
    • Vulkan実装では,VkFramebuffer, VkRenderPassをそれぞれ保持します。a3dにはVkRenderPassの明示的な切り替えメソッドは提供されないため,ICommandList::SetFrameBuffer()メソッドでレンダーパスの開始・終了が実行されます。
  • IPipelineState
    パイプラインで使用するレンダーステートやシェーダを保持するインタフェースです。

    • D3D12実装では,ID3D12PipelineStateとD3D_PRIMITIVE_TOPOLOGYを保持します。a3dライブラリでは,ID3D12GraphicsCommandList::IASetPrimitiveTopology()に対応するメソッドは提供されません。そのため,あるプリミティブトポロジーから別のプリミティブトポロジーを変更するには,別のパイプラインステートオブジェクトを用意する必要があります。この仕様制限はVulkanがパイプラインステート単位でしかトポロジーを変更できない仕様に由来します。
    • Vulkan実装では,VkPipeline,VkPiplineCacheをそれぞれ保持します。
  • IQueryPool
    クエリするデータを複数持つクエリプールです。

    • D3D12実装では,ID3D12QueryHeapを保持します。
    • Vulkan実装では,VkQueryPoolを保持します。
  • IQueue
    コマンドキューです。GPU上で実行するコマンドを登録するためのインタフェースです。

    • D3D12実装では,ID3D12CommandQueueとID3D12CommandListの配列,ID3D12Fenceをそれぞれ保持します。
    • Vulkan実装では,VkQueueとVkCommandBufferの配列,VkSemaphoreをそれぞれ保持します。
  • ISampler
    サンプラーインタフェースです。テクスチャフェッチする際のサンプリング方法の定義を行います。

    • D3D12実装では,D3D12_GPU_DESCRIPTOR_HANDLEを保持します。
    • Vulkan実装では,VkSamplerを保持します。
  • ISwapChain
    スワップチェインインタフェースです。画面へ出力するための1つまたは複数のサーフェイスを保持し,ダブルバッファリングやトリプルバッファリングといったバッファリングを行います。

    • D3D12実装では,HDR10に対応するためIDXGISwapChain4と,ITextureの配列をそれぞれ保持します。
    • Vulkan実装では,VkSufraceKHR, VkSwapChainKHR, ITextureの配列をそれぞれ保持します。
  • ITexture
    テクスチャインタフェースです。テクスチャとして利用するためのメモリを保持するインタフェースです。

    • D3D12実装では,ID3D12Resourceを保持します。
    • Vulkan実装では,VkImage, VkDeviceMemoryをそれぞれ保持します。
  • ITextureView
    テクスチャビューインタフェースです。レンダーターゲットビューや深度ステンシルビュー,シェーダリソースビューやアンオーダードアクセスビューのようなビューオブジェクトを一般化したインタフェースです。

    • D3D12実装では,D3D12_CPU_DESCRIPTOR_HANDLE, D3D12_GPU_DESCRIPTOR_HANDLEと,Textureクラス(ITextureインタフェースの具象化クラス)へのポインタをそれぞれ保持します。
    • Vulkan実装では,VkIamgeView, Textureクラスへのポインタをそれぞれ保持します。

ライブラリの使い方

ビルド方法

肝心のライブラリの使い方です。現在 Github ではビルド済みバイナリを配布していないので,各自でライブラリのソースコードをダウンロード後にビルドをする必要があります。
ビルドするには予め以下のものをインストールしておく必要があります。

  • MSBuild 14.0
  • Visual Studio 2015 Update 3
  • Windows SDK
  • Vulkan SDK

ビルド方法についてですが,バッチビルドと個別にソシューションファイルを開いてビルドする方法があります。
バッチビルドでは,MSBuild 14.0を使用します。project/build.batを実行することで,binフォルダにスタティックライブラリが生成されます。一括ビルドを行いたい場合は,こちらのバッチを利用するのがお勧めです。
ソリューションファイルからビルドするには,project/d3d12フォルダと,project/vulkanフォルダにあるa3d.slnファイルをVisual Studioで読み込みビルドを実行してください。

組み込み方法

アプリケーションで使用するためにはa3d.hをインクルードすることと,ビルドで出来上がったスタティックライブラリをリンクする必要があります。

Direct3D 12版を使用する場合は次のファイルをリンクする必要があります。

  • a3d_d3d12d.lib (デバッグ版)
  • a3d_d3d12.lib (リリース版)
  • d3d12.lib
  • dxgi.lib

Vulkan版を使用する場合は次のファイルをリンクする必要があります。

  • a3d_vkd.lib (デバッグ版)
  • a3d_vk.lib (リリース版)
  • vulkan-1.lib

実装例

ここでは,三角形ポリゴンを描画するサンプルを説明してみます。

実装にあたって先に注意点を言っておきます。
a3dライブラリはビルド済みのシェーダバイナリを受け取る設計になっています。そのため,各プラットフォームごとに実行するシェーダバイナリを個別に用意する必要があります。
D3D12であれば,fxc.exeで作成されたシェーダバイナリ。
Vulkanであれば,glslangValidator.exeで作成されたSPIR-Vのシェーダバイナリとなります。

まず,描画用のシェーダを用意しておきます。
d3d12を使用する場合は,下記のようなHLSLソースコードを用意し,fxc.exeでコンパイルします。

頂点シェーダ[HLSL] simpleVS.hlsl
///////////////////////////////////////////////////////////////////////////////////////////////////
// VSInput structure
///////////////////////////////////////////////////////////////////////////////////////////////////
struct VSInput 
{
    float3 Position : POSITION;     // 位置座標です.
    float4 Color    : COLOR;        // 頂点カラーです.
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// VSOutput structure
///////////////////////////////////////////////////////////////////////////////////////////////////
struct VSOutput
{
    float4 Position : SV_POSITION;  // 位置座標です.
    float4 Color    : COLOR;        // 頂点カラーです.
};

//-------------------------------------------------------------------------------------------------
//      頂点シェーダメインエントリーポイントです.
//-------------------------------------------------------------------------------------------------
VSOutput main(const VSInput input)
{
    VSOutput output = (VSOutput)0;

    float4 localPos = float4(input.Position, 1.0f);

    output.Position = localPos;
    output.Color    = input.Color;

    return output;
}
ピクセルシェーダ[HLSL] simplePS.hlsl
///////////////////////////////////////////////////////////////////////////////////////////////////
// VSOutput structure
///////////////////////////////////////////////////////////////////////////////////////////////////
struct VSOutput
{
    float4 Position : SV_POSITION;
    float4 Color    : COLOR;
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// PSOutput structure
///////////////////////////////////////////////////////////////////////////////////////////////////
struct PSOutput
{
    float4 Color : SV_TARGET0;
};

//-------------------------------------------------------------------------------------------------
//      ピクセルシェーダメインエントリーポイントです.
//-------------------------------------------------------------------------------------------------
PSOutput main(VSOutput input)
{
    PSOutput output = (PSOutput)0;

    output.Color = input.Color;

    return output;
}

Vulkanを使用する場合は,下記のようなGLSLソースコードを用意し,Vulkan SDKに付属のglslangValidator.exeでSPIR-V形式に変換します。

頂点シェーダ[GLSL] simpleVS.vert
#version 450
#extension GL_ARB_separate_shader_objects  : enable
#extension GL_ARB_shading_language_420pack : enable

//-------------------------------------------------------------------------------------------------
// Input Definitions.
//-------------------------------------------------------------------------------------------------
layout(location = 0) in vec3 InputPosition; // 位置座標.
layout(location = 1) in vec4 InputColor;    // 頂点カラー.

//-------------------------------------------------------------------------------------------------
// Output Definitions.
//-------------------------------------------------------------------------------------------------
layout(location = 0) out vec4 OutputColor;  // 頂点カラー

out gl_PerVertex
{
    vec4 gl_Position;   // 位置座標.
};

//-------------------------------------------------------------------------------------------------
//      頂点シェーダメインエントリーポイントです.
//-------------------------------------------------------------------------------------------------
void main()
{
    vec4 localPosition = vec4(InputPosition, 1.0f);

    gl_Position = localPosition;
    OutputColor = InputColor;

    // Flip Y-Coordinate.
    gl_Position.y = -gl_Position.y;
}
フラグメントシェーダ[GLSL] simplePS.frag
#version 450
#extension GL_ARB_separate_shader_objects  : enable
#extension GL_ARB_shading_language_420pack : enable

//-------------------------------------------------------------------------------------------------
// Input Definitions.
//-------------------------------------------------------------------------------------------------
layout(location = 0) in vec4 InputColor;

//-------------------------------------------------------------------------------------------------
// Output Definitions.
//-------------------------------------------------------------------------------------------------
layout(location = 0) out vec4 ColorTarget0;


//-------------------------------------------------------------------------------------------------
//      フラグメントシェーダメインエントリーポイントです.
//-------------------------------------------------------------------------------------------------
void main()
{
    ColorTarget0 = InputColor;
}

続いて,a3dの説明に入ります。使う際の大きな処理の流れとしては…

  • システムの初期化処理
  • 各オブジェクトの生成
  • 描画コマンド生成・実行
  • 各オブジェクトの破棄
  • システムの終了処理

となります。
この各項目について説明してみます。

システムの初期化処理

a3dライブラリを使うためには,アプリケーション側からa3dシステムにアロケータを設定しておく必要があります。
アロケータはa3d::IAllocatorインタフェースとして定義されています。最も単純な実装例(手抜き実装)は次のようになります。きちんとした実装をする際はDandyManiaさんが書かれている記事を参考にされると良いと思います。

///////////////////////////////////////////////////////////////////////////////////////////////////
// Allocator class
///////////////////////////////////////////////////////////////////////////////////////////////////
class Allocator : a3d::IAllocator
{
    //=============================================================================================
    // list of friend classes and methods.
    //=============================================================================================
    /* NONTHING */

public:
    //=============================================================================================
    // public variables.
    //=============================================================================================
    /* NOHTING */

    //=============================================================================================
    // public methods.
    //=============================================================================================

    //---------------------------------------------------------------------------------------------
    //      メモリを確保します.
    //---------------------------------------------------------------------------------------------
    void* Alloc(size_t size, size_t alignment) noexcept override
    {
        auto allocSize = a3d::RoundUp(size, alignment);
        return malloc(size);
    }

    //---------------------------------------------------------------------------------------------
    //      メモリを再確保します.
    //---------------------------------------------------------------------------------------------
    void* Realloc(void* ptr, size_t size, size_t alignment) noexcept override
    {
        auto allocSize = a3d::RoundUp(size, alignment);
        return realloc(ptr, allocSize);
    }

    //---------------------------------------------------------------------------------------------
    //      メモリを解放します.
    //---------------------------------------------------------------------------------------------
    void Free(void* ptr) noexcept override
    { free(ptr); }
} g_Allocator;

このアプリケーション側で定義したアロケータへのポインタをa3d::InitSystem()メソッドを通じてa3dライブラリに設定します。

    // グラフィックスシステムの初期化.
    if (!a3d::InitSystem(reinterpret_cast<a3d::IAllocator*>(&g_Allocator)))
    { return false; }

各オブジェクトの生成

ライブラリの初期化処理に成功したら,次はデバイスを生成します。
生成するデバイスの設定は,a3d::DeviceDesc構造体で行います。この構造体の定義は下記のとおりです。

///////////////////////////////////////////////////////////////////////////////////////////////////
// DeviceDesc structure
//! @brief  デバイスの設定です.
///////////////////////////////////////////////////////////////////////////////////////////////////
struct DeviceDesc
{
    uint32_t        MaxShaderResourceCount;         //!< 最大シェーダリソース数です.
    uint32_t        MaxSamplerCount;                //!< 最大サンプラー数です.
    uint32_t        MaxColorTargetCount;            //!< 最大カラーターゲット数です.
    uint32_t        MaxDepthTargetCount;            //!< 最大深度ターゲット数です.
    uint32_t        MaxGraphicsQueueSubmitCount;    //!< グラフィックスキューへの最大サブミット数です.
    uint32_t        MaxComputeQueueSubmitCount;     //!< コンピュートキューへの最大サブミット数です.
    uint32_t        MaxCopyQueueSubmitCount;        //!< コピーキューへの最大サブミット数です.
    bool            EnableDebug;                    //!< デバッグモードを有効にします.
};

デバイスの生成は次のように,a3d::CreateDevice()メソッドを使用します。

    // デバイスの生成.
    {
        a3d::DeviceDesc desc = {};

        // 最大ディスクリプタ数を設定.
        desc.MaxColorTargetCount            = 2;
        desc.MaxShaderResourceCount         = 1;

        // 最大サブミット数を設定.
        desc.MaxGraphicsQueueSubmitCount    = 256;
        desc.MaxCopyQueueSubmitCount        = 256;
        desc.MaxComputeQueueSubmitCount     = 256;

        // デバイスを生成.
        if (!a3d::CreateDevice(&desc, nullptr, &g_pDevice))
        { return false; }
    }

このサンプルでは,ダブルバッファリングするためカラーターゲット数を2に設定しています。
コマンドキューへの登録可能なコマンドリストの数は256に設定しています。この値は,アプリケーション側で使用する数に応じて調整が必要です。

デバイスを生成したら,コマンド実行のためにグラフィックス用のコマンドキューをデバイスから取得しておきます。

    // コマンドキューを取得.
    g_pDevice->GetGraphicsQueue(&g_pGraphicsQueue);

コマンドキューを取得したら,つぎはスワップチェインを生成します。

    // スワップチェインの生成.
    {
    #if SAMPLE_IS_VULKAN
        auto format = a3d::RESOURCE_FORMAT_B8G8R8A8_UNORM;
    #else
        auto format = a3d::RESOURCE_FORMAT_R8G8B8A8_UNORM;
    #endif

        a3d::SwapChainDesc desc = {};
        desc.Extent.Width   = g_pApp->GetWidth();
        desc.Extent.Height  = g_pApp->GetHeight();
        desc.Format         = format;
        desc.MipLevels      = 1;
        desc.SampleCount    = 1;
        desc.BufferCount    = 2;
        desc.SyncInterval   = 1;
        desc.InstanceHandle = g_pApp->GetInstanceHandle();
        desc.WindowHandle   = g_pApp->GetWindowHandle();

        if (!g_pDevice->CreateSwapChain(&desc, &g_pSwapChain))
        { return false; }

        // スワップチェインからバッファを取得.
        g_pSwapChain->GetBuffer(0, &g_pColorBuffer[0]);
        g_pSwapChain->GetBuffer(1, &g_pColorBuffer[1]);

        a3d::TextureViewDesc viewDesc = {};
        viewDesc.Dimension          = a3d::VIEW_DIMENSION_TEXTURE2D;
        viewDesc.Format             = format;
        viewDesc.TextureAspect      = a3d::TEXTURE_ASPECT_COLOR;
        viewDesc.MipSlice           = 0;
        viewDesc.MipLevels          = desc.MipLevels;
        viewDesc.FirstArraySlice    = 0;
        viewDesc.ArraySize          = 1;
        viewDesc.ComponentMapping.R = a3d::TEXTURE_SWIZZLE_R;
        viewDesc.ComponentMapping.G = a3d::TEXTURE_SWIZZLE_G;
        viewDesc.ComponentMapping.B = a3d::TEXTURE_SWIZZLE_B;
        viewDesc.ComponentMapping.A = a3d::TEXTURE_SWIZZLE_A;

        for(auto i=0; i<2; ++i)
        {
            if (!g_pDevice->CreateTextureView(g_pColorBuffer[i], &viewDesc, &g_pColorView[i]))
            { return false; }
        }
    }

手元のPCだと,VulkanがR8G8B8A8_UNORM形式をサポートしていないため,スワップチェインの作成に失敗するということが発生しました。そのため,このサンプルではリソースフォーマットをプラットフォームに応じて指定する分岐を入れています。
スワップチェインの生成に成功したら,フレームバッファに割り当てるテクスチャビューを作成しておきます。テクスチャビューを生成したらフレームバッファを生成します。

    // フレームバッファの生成
    {
        // フレームバッファの設定.
        a3d::FrameBufferDesc desc = {};
        desc.ColorCount         = 1;
        desc.pColorTargets[0]   = g_pColorView[0];
        desc.pDepthTarget       = nullptr;

        // 1枚目のフレームバッファを生成.
        if (!g_pDevice->CreateFrameBuffer(&desc, &g_pFrameBuffer[0]))
        { return false; }

        // 2枚目のフレームバッファを生成.
        desc.pColorTargets[0] = g_pColorView[1];
        if (!g_pDevice->CreateFrameBuffer(&desc, &g_pFrameBuffer[1]))
        { return false; }
    }

ここではダブルバッファリングするため,2枚分のフレームバッファを生成しています。
フレームバッファを生成したら,コマンドリストとフェンスを生成しておきます。

    // コマンドリストを生成.
    {
        for(auto i=0; i<2; ++i)
        {
            if (!g_pDevice->CreateCommandList(a3d::COMMANDLIST_TYPE_DIRECT, nullptr, &g_pCommandList[i]))
            { return false; }
        }
    }

    // フェンスを生成.
    {
        if (!g_pDevice->CreateFence(&g_pFence))
        { return false; }
    }

次は,三角形描画のための頂点バッファを準備します。頂点データは先ほどシェーダを用意したので,シェーダと同じになるように下記のように定義します。

///////////////////////////////////////////////////////////////////////////////////////////////////
// Vertex structure
///////////////////////////////////////////////////////////////////////////////////////////////////
struct Vertex
{
    float Position[3];      //!< 位置座標です.
    float Color   [4];      //!< 頂点カラーです.
};

頂点バッファはa3d::BufferDesc構造体のUsageメンバ変数にa3d::RESOURCE_USAGE_VERTEX_BUFFERを指定することで頂点バッファとして利用可能になります。生成例は次の通りです。

    // 頂点バッファを生成.
    {
        Vertex vertices[] = {
            {  0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f },
            { -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f },
            {  0.0f,  0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f },
        };

        a3d::BufferDesc desc = {};
        desc.Size                           = sizeof(vertices);
        desc.Stride                         = sizeof(Vertex);
        desc.InitState                      = a3d::RESOURCE_STATE_GENERAL;
        desc.Usage                          = a3d::RESOURCE_USAGE_VERTEX_BUFFER;
        desc.HeapProperty.Type              = a3d::HEAP_TYPE_UPLOAD;
        desc.HeapProperty.CpuPageProperty   = a3d::CPU_PAGE_PROPERTY_DEFAULT;

        if ( !g_pDevice->CreateBuffer(&desc, &g_pVertexBuffer) )
        { return false; }

        auto ptr = g_pVertexBuffer->Map();
        if ( ptr == nullptr )
        { return false; }

        memcpy( ptr, vertices, sizeof(vertices) );

        g_pVertexBuffer->Unmap();
    }

a3d::IBufferへのデータ設定は,Map()メソッドでポインタを取得して,memcpy()などでデータを書き込みます。書き込みが終わったらUnmap()メソッドを呼び出し,メモリマッピングを解除します。

つぎにパイプラインステートの生成に入りますが,パイプラインステートを生成するために,ディスクリプタセットレイアウトが必要になります。a3dライブラリでは,Vulkanへ対応するためディスクリプタセットを一切使わない状態でもディスクリプタセットレイアウトを必ず生成する必要があります。ディスクリプタセットを使わない場合は次のようにディスクリプタセットレイアウトを生成してください。

    // ディスクリプタセットレイアウトを生成します.
    {
        a3d::DescriptorSetLayoutDesc desc = {};
        desc.EntryCount  = 0;
        desc.MaxSetCount = 0;

        if (!g_pDevice->CreateDescriptorSetLayout(&desc, &g_pDescriptorSetLayout))
        { return false; }
    }

シェーダやブレンドステート・カリングなどの設定はパイプラインステートで設定します。パイプラインステートの生成は次のようになります。

    // グラフィックスパイプラインステートを生成します.
    {
        // 入力要素です.
        a3d::InputElementDesc inputElements[] = {
            { "POSITION", 0, 0, a3d::RESOURCE_FORMAT_R32G32B32_FLOAT   , 0  },
            { "COLOR"   , 0, 1, a3d::RESOURCE_FORMAT_R32G32B32A32_FLOAT, 12 },
        };

        // 入力ストリームです.
        a3d::InputStreamDesc inputStream = {};
        inputStream.ElementCount    = 2;
        inputStream.pElements       = inputElements;
        inputStream.StreamIndex     = 0;
        inputStream.StrideInBytes   = sizeof(Vertex);
        inputStream.InputClass      = a3d::INPUT_CLASSIFICATION_PER_VERTEX;

        // 入力レイアウトです.
        a3d::InputLayoutDesc inputLayout = {};
        inputLayout.StreamCount = 1;
        inputLayout.pStreams    = &inputStream;

        // ステンシルステートです.
        a3d::StencilState stencilState = {};
        stencilState.StencilFailOp      = a3d::STENCIL_OP_KEEP;
        stencilState.StencilDepthFailOp = a3d::STENCIL_OP_KEEP;
        stencilState.StencilFailOp      = a3d::STENCIL_OP_KEEP;
        stencilState.StencilCompareOp   = a3d::COMPARE_OP_NEVER;

        // グラフィックスパイプラインステートを設定します.
        a3d::GraphicsPipelineStateDesc desc = {};

        // シェーダの設定.
        desc.VertexShader = vs;
        desc.PixelShader  = ps;

        // ブレンドステートの設定.
        desc.BlendState.IndependentBlendEnable          = false;
        desc.BlendState.LogicOpEnable                   = false;
        desc.BlendState.LogicOp                         = a3d::LOGIC_OP_NOOP;
        for(auto i=0; i<8; ++i)
        {
            desc.BlendState.ColorTarget[i].BlendEnable      = false;
            desc.BlendState.ColorTarget[i].SrcBlend         = a3d::BLEND_FACTOR_ONE;
            desc.BlendState.ColorTarget[i].DstBlend         = a3d::BLEND_FACTOR_ZERO;
            desc.BlendState.ColorTarget[i].BlendOp          = a3d::BLEND_OP_ADD;
            desc.BlendState.ColorTarget[i].SrcBlendAlpha    = a3d::BLEND_FACTOR_ONE;
            desc.BlendState.ColorTarget[i].DstBlendAlpha    = a3d::BLEND_FACTOR_ZERO;
            desc.BlendState.ColorTarget[i].BlendOpAlpha     = a3d::BLEND_OP_ADD;
            desc.BlendState.ColorTarget[i].EnableWriteR     = true;
            desc.BlendState.ColorTarget[i].EnableWriteG     = true;
            desc.BlendState.ColorTarget[i].EnableWriteB     = true;
            desc.BlendState.ColorTarget[i].EnableWriteA     = true;
        }

        // ラスタライザ―ステートの設定.
        desc.RasterizerState.PolygonMode                = a3d::POLYGON_MODE_SOLID;
        desc.RasterizerState.CullMode                   = a3d::CULL_MODE_NONE;
        desc.RasterizerState.FrontCounterClockWise      = false;
        desc.RasterizerState.DepthBias                  = 0;
        desc.RasterizerState.DepthBiasClamp             = 0.0f;
        desc.RasterizerState.SlopeScaledDepthBais       = 0;
        desc.RasterizerState.DepthClipEnable            = true;
        desc.RasterizerState.EnableConservativeRaster   = false;

        // マルチサンプルステートの設定.
        desc.MultiSampleState.EnableAlphaToCoverage = false;
        desc.MultiSampleState.EnableMultiSample     = false;
        desc.MultiSampleState.SampleCount           = 1;

        // 深度ステンシルステートの設定.
        desc.DepthStencilState.DepthTestEnable      = false;
        desc.DepthStencilState.DepthWriteEnable     = false;
        desc.DepthStencilState.DepthCompareOp       = a3d::COMPARE_OP_NEVER;
        desc.DepthStencilState.StencilTestEnable    = false;
        desc.DepthStencilState.StencllReadMask      = 0;
        desc.DepthStencilState.StencilWriteMask     = 0;
        desc.DepthStencilState.FrontFace            = stencilState;
        desc.DepthStencilState.BackFace             = stencilState;

        // テッセレーションステートの設定.
        desc.TessellationState.PatchControlCount = 0;

        // 入力アウトの設定.
        desc.InputLayout = inputLayout;

        // ディスクリプタセットレイアウトの設定.
        desc.pLayout = g_pDescriptorSetLayout;

        // プリミティブトポロジーの設定.
        desc.PrimitiveTopology = a3d::PRIMITIVE_TOPOLOGY_TRIANGLELIST;

        // フレームバッファの設定.
        desc.pFrameBuffer = g_pFrameBuffer[0];

        // キャッシュ済みパイプラインステートの設定.
        desc.pCachedPSO = nullptr;

        // グラフィックスパイプラインステートの生成.
        if (!g_pDevice->CreateGraphicsPipeline(&desc, &g_pPipelineState))
        {
            DisposeShaderBinary(vs);
            DisposeShaderBinary(ps);
            return false;
        }
    }

パイプラインステートの設定の際に注意してほしい点は,入力要素の設定です。前述したように,a3dは共通のシェーダバイナリがなくシェーダ周りはネイティブに依存する設計になってしまっています。HLSLはセマンティクス名で,入力データの解決ができますが,GLSLにはセマンティクスがなくロケーション番号で解決する必要があります。そのため,a3dではどちらの場合でも対応できるようするため,セマンティクス名とロケーション番号の両方を指定するAPIとなっています。

///////////////////////////////////////////////////////////////////////////////////////////////////
// InputElementDesc structure
//! @brief  入力要素の設定です.
///////////////////////////////////////////////////////////////////////////////////////////////////
struct InputElementDesc
{
    const char*         SemanticName;   //!< セマンティックです.
    uint32_t            SemanticIndex;  //!< セマンティックインデックスです.
    uint32_t            BindLocation;   //!< バインドロケーションです.
    RESOURCE_FORMAT     Format;         //!< リソースフォーマットです.
    uint32_t            OffsetInBytes;  //!< 先頭要素からオフセットです.
};

d3d12とvulkanの両プラットフォームをサポートする場合は,使用するシェーダに合わせて適切な数値を設定する必要があります。特に,この部分はd3d12のみ,vulkanのみで動かしている場合は気づきづらい点ですので,ご注意ください。
現在,この設計上の問題点を解決するためにシェーダバイナリ作成ツール導入を検討を進めています。技術的な問題点が解決次第,こうした手間を無くすためのAPI変更を行う予定です。

パイプラインステートを生成したら,後はビューポートとシザー矩形として設定する値を準備しておきます。

//a3d::Viewport g_Viewport = {}; //!< ビューポートです.
//a3d::Rect     g_Scissor  = {}; //!< シザー矩形です.

    // ビューポートの設定.
    g_Viewport.X        = 0.0f;
    g_Viewport.Y        = 0.0f;
    g_Viewport.Width    = static_cast<float>(g_pApp->GetWidth());
    g_Viewport.Height   = static_cast<float>(g_pApp->GetHeight());
    g_Viewport.MinDepth = 0.0f;
    g_Viewport.MaxDepth = 1.0f;

    // シザー矩形の設定.
    g_Scissor.Offset.X      = 0;
    g_Scissor.Offset.Y      = 0;
    g_Scissor.Extent.Width  = g_pApp->GetWidth();
    g_Scissor.Extent.Height = g_pApp->GetHeight();

これで三角形描画に使用するオブジェクトが揃いました。

描画コマンドの生成・実行

三角形を描画してみます。a3dライブラリで描画処理を行う際は,最初にa3d::ICommandList::Begin()を呼び出して,最後にa3d::ICommandList::End()を呼び出します。Begin()とEnd()の間に呼ばれたメソッドがコマンドリストに描画コマンドとして蓄積されます。この蓄積した描画コマンドをGPUで実行するためには,a3d::IQueue::Submit()メソッドで,コマンドキューにコマンドリストを登録します。GPU上で実行するコマンドリストをすべてコマンドキューに登録したら,a3d::IQueue::Execute()を呼び出してコマンドを実行します。Execute()メソッドを呼び出す際の引数にa3d::IFenceを渡すことで,コマンド実行が終了したかどうかをa3d::IFence::IsSignaled()で確認できるようになります。コマンドが完了していない場合に,待機処理を実行する場合は,a3d::IFence::Wait()メソッドが使えます。コマンドの実行が完了したら,a3d::ISwapChain::Present()メソッドを呼び出すことで画面への表示処理が実行されます。この表示処理の際に,スワップチェインをダブルバッファやトリプルバッファとしたバッファリングできる設定にしている場合は,内部でバッファの交換処理が実行されます。現在のバッファ番号を取得するためには,a3d::ISwapChain::GetCurrentBufferIndex()メソッドが使用できます。

//-------------------------------------------------------------------------------------------------
//      A3Dによる描画処理を行います.
//-------------------------------------------------------------------------------------------------
void DrawA3D()
{
    // バッファ番号を取得します.
    auto idx = g_pSwapChain->GetCurrentBufferIndex();

    // コマンドの記録を開始します.
    auto pCmd = g_pCommandList[idx];
    pCmd->Begin();

    // 書き込み用のバリアを設定します.
    pCmd->TextureBarrier(g_pColorBuffer[idx], a3d::RESOURCE_STATE_COLOR_WRITE);

    // フレームバッファを設定します.
    pCmd->SetFrameBuffer(g_pFrameBuffer[idx]);

    // フレームバッファをクリアします.
    a3d::ClearColorValue clearColor = {};
    clearColor.Float[0] = 0.25f;
    clearColor.Float[1] = 0.25f;
    clearColor.Float[2] = 0.25f;
    clearColor.Float[3] = 1.0f;
    pCmd->ClearFrameBuffer(1, &clearColor, nullptr);

    {
        // ディスクリプタセットレイアウトを設定します.
        pCmd->SetDescriptorSetLayout(g_pDescriptorSetLayout);

        // パイプラインステートを設定します.
        pCmd->SetPipelineState(g_pPipelineState);

        // ビューポートとシザー矩形を設定します.
        // NOTE : ビューポートとシザー矩形の設定は,必ずSetPipelineState() の後である必要があります.
        pCmd->SetViewports(1, &g_Viewport);
        pCmd->SetScissors (1, &g_Scissor);

        // 頂点バッファを設定します.
        pCmd->SetVertexBuffers(0, 1, &g_pVertexBuffer, nullptr);

        // 三角形を描画します.
        pCmd->DrawInstanced(3, 1, 0, 0);
    }

    // 表示用のバリアを設定する前に,フレームバッファの設定を解除する必要があります.
    pCmd->SetFrameBuffer(nullptr);

    // 表示用にバリアを設定します.
    pCmd->TextureBarrier(g_pColorBuffer[idx], a3d::RESOURCE_STATE_PRESENT);

    // コマンドリストへの記録を終了します.
    pCmd->End();

    // コマンドキューに登録します.
    g_pGraphicsQueue->Submit(pCmd);

    // コマンドを実行します.
    g_pGraphicsQueue->Execute(g_pFence);

    // コマンドを実行完了を待機します.
    if (!g_pFence->IsSignaled())
    { g_pFence->Wait(UINT32_MAX); }

    // 画面に表示します.
    g_pSwapChain->Present();
}

描画処理で気を付けてほしいのは,フレームバッファへの書き込みを終わらわせておきたい箇所で,SetFrameBuffer(nullptr)のようにフレームバッファの設定解除処理を入れておく必要があることです。
この設定解除が必要なのは,a3dライブラリのVulkan対応のためです。a3dライブラリでは抽象化するために,vkRenderPassをa3d::IFrameBufferで保持する実装となっています。通常Vulkan APIでは,vkCmdEndRenderPass()のようにレンダーパスを明示的に終了する必要があります。a3dライブラリでは,同等の処理を行うためにSetFrameBuffer()メソッドに,nullptrが設定されており,レンダーパスが開始状態であれば,vkCmEndRenderPass()を呼び出すといった仕様になっています。

//-------------------------------------------------------------------------------------------------
//      フレームバッファを設定します.
//-------------------------------------------------------------------------------------------------
void CommandList::SetFrameBuffer(IFrameBuffer* pBuffer)
{
    if (pBuffer == nullptr)
    {
        // フレームバッファがバインド済みであればレンダーパスを終わらせる.
        if (m_pFrameBuffer != nullptr)
        {
            vkCmdEndRenderPass(m_CommandBuffer);
            m_pFrameBuffer = nullptr;
        }

        return;
    }

    auto pWrapFrameBuffer = reinterpret_cast<FrameBuffer*>(pBuffer);
    A3D_ASSERT(pWrapFrameBuffer != nullptr);

    // 同じフレームバッファであればコマンドは出さない.
    if (m_pFrameBuffer == pWrapFrameBuffer)
    { return; }

    // フレームバッファがバインド済みであればレンダーパスを終わらせる.
    if (m_pFrameBuffer != nullptr)
    { vkCmdEndRenderPass(m_CommandBuffer); }

    pWrapFrameBuffer->Bind( this );
    m_pFrameBuffer = pWrapFrameBuffer;
}

vkCmdEndRenderPass()のようなインタフェースを設けていないため,vkCmdEndRenderPass()を呼び出すためにSetFrameBuffer(nullptr)が必要になります。ここはDirect3D 12に慣れている方だと,わかりづらく見逃してしまいVulkanでは動作しない恐れがあるため注意してください。

各オブジェクトの破棄

オブジェクトの破棄は,Release()メソッドを使用します。前述したように参照カウンタ形式をとっていますので,参照カウンタが0になった場合に破棄処理が実行されます。

    // ダブルバッファリソースの破棄.
    for(auto i=0; i<2; ++i)
    {
        // フレームバッファの破棄.
        a3d::SafeRelease(g_pFrameBuffer[i]);

        // カラービューの破棄.
        a3d::SafeRelease(g_pColorView[i]);

        // カラーバッファの破棄.
        a3d::SafeRelease(g_pColorBuffer[i]);

        // コマンドリストの破棄.
        a3d::SafeRelease(g_pCommandList[i]);
    }

    // パイプラインステートの破棄.
    a3d::SafeRelease(g_pPipelineState);

    // 頂点バッファの破棄.
    a3d::SafeRelease(g_pVertexBuffer);

    // ディスクリプタセットレイアウトの破棄.
    a3d::SafeRelease(g_pDescriptorSetLayout);

    // フェンスの破棄.
    a3d::SafeRelease(g_pFence);

    // スワップチェインの破棄.
    a3d::SafeRelease(g_pSwapChain);

    // グラフィックスキューの破棄.
    a3d::SafeRelease(g_pGraphicsQueue);

    // デバイスの破棄.
    a3d::SafeRelease(g_pDevice);

システムの終了処理

a3dシステムを終了させるためには,全てのオブジェクトを破棄した状態でa3d::TermSystem()を呼び出します。

    // グラフィックスシステムの終了処理.
    a3d::TermSystem();

この処理は,内部でアロケータをnullptrに設定します。

//-------------------------------------------------------------------------------------------------
//      グラフィックスシステムの終了処理を行います.
//-------------------------------------------------------------------------------------------------
void A3D_APIENTRY TermSystem()
{
    A3D_ASSERT( g_AllocCounter == 0 );
    g_pAllocator = nullptr;
}

そのため,a3d::TermSystem()呼び出し以後は,a3dライブラリのメソッドは呼び出さないようにお願いします。

その他のサンプルについて

Githubに随時サンプルを追加しています。
https://github.com/ProjectAsura/asura-SDK/tree/master/a3d

現在は下記のようなサンプルを用意しています。

  • ClearColor
    画面をクリアするだけの単純なサンプルです。
    001_ClearColor.png

  • DrawPolygon
    ポリゴンを表示するサンプルです。
    002_DrawPolygon.png

  • DrawIndexed
    インデックスバッファを用いてポリゴンを表示するサンプルです。
    003_DrawIndexed.png

  • ConstantBuffer
    定数バッファを用いて,ポリゴンを回転させるサンプルです。
    004_ConstantBuffer.png

  • DepthBuffer
    深度バッファを用いて,手前と奥のポリゴンを表示するサンプルです。
    005_DepthBuffer.png

  • DrawTexture
    テクスチャを描画するサンプルです。
    006_DrawTexture.png

  • ImGuiSample
    ImGuiの組み込みサンプルです。
    007_ImGuiSample.png

  • RenderingTexture
    オフスクリーンレンダリングにした描画結果をテクスチャとして表示するサンプルです。
    008_RenderingTexture.png

既知の問題点

開発中のため,まだ全機能の実装チェックが済んでいない状態です。
特に,クエリとコンピュートシェーダ周り,マルチスレッドでの並列動作は動作確認が済んでいません。
そのため,不具合が存在する可能性があり,不具合があった場合に予告なく仕様変更する恐れがあります。
(誰も使ってくれる人はいないと思いますが…)使用する場合は,ご注意ください。

a3dライブラリの今後

実装した機能をきちんと枯れさせることを第1目標にして頑張っていきたいなと思います。
それが終わったらシェーダ周りが設計的に貧弱なので,次はそのあたりの改善をやっていこうかと思います。
パイプラインステート周りは,サンプル見て分かるように大分設定が面倒なので,ツールで事前生成,ランタイムでは読み込むだけという簡略化の仕組みを考えています。今は仕事で忙しいので,だいぶ先になってしまいそうですが,いくつかやりたいことはあるので,また機会があれば形になった際に,Qittaやブログでご報告させて頂こうかなと思います。
また,a3dライブラリはエンジンを作るための単なる土台なので,今後a3dを基に上位レイヤーとなるグラフィックスライブラリを作成する予定です。こちらのライブラリでは,メッシュやテクスチャの読み込み,ライティングやシャドウマップといった各種のグラフィックスアルゴリズムを実装がされる予定です。

最後に

大分中途半端になってしまいましたが…
開発中のライブラリ「a3d」について紹介させていただきました。
もし使ってくれる方がいたら,フィードバックを頂けると非常に有難いです!
  
  
明日のアドベントカレンダーは
emadurandal さんの
WebGLライブラリ「GLBoost」とは
です。

Comments Loading...