ここぷろ!

私がプログラミングで学んだこと、行き詰った事などを書いていきます。たまに個人的なことも載せるかも

任意のInterfaceを実装したクラスを取得するメソッドを実装しました


 こんにちは、ここあです。


 前回、インタフェース活用例でちらっとお話した、自前の拡張メソッドを紹介したいと思います。
 これに関しては、前回の記事で紹介したものと違い、インタフェースについて調べてるときにどこかで見た気がしますが……?
 もしかしたら誰かが同じような実装をしているかもしれませんが、備忘録として書いておこうと思います。

実装詳細

きっかけ

 今回紹介するメソッドですが、複数シーンを同時に運用している関係上、各シーンごとのオブジェクトを、各管理クラスが明確に把握した状態で運用できるように、考えた結果行きついた答えのひとつです。これを関数の呼び出しのみで行いたかった為に実装しました。


 前回の記事Unityゲーム開発で役立つインタフェース活用例 - ここぷろ!で紹介した通り、拡張メソッドになります。
 ジェネリクスを使い、任意のインタフェースを実装した、全てのクラスを取得してくる拡張メソッドです。


 ジェネリクスについて、私はこちらのサイトを参考に勉強させていただきました。 
ufcpp.net

処理手順

 基本的にシーンが生成された直後のAwake()のタイミングで動かすことを前提として作っています。


  1. まず、存在する全てのコンポーネントがアタッチされたオブジェクトを取得。
  2. as演算子により、指定のインタフェースにアップキャストできるか判定。
  3. ゲーム動作中のメモリを少しでも確保するために、念のため可変長のListから、固定長配列に格納して返しています。


 このメソッドは、Manager等の管理クラスから呼ばれることになる為、使いやすさを考慮してstaticメソッドとして定義しました。
 使用する場合は、以下の様に一行の関数呼び出しで使えるようになっています。

// 格納配列変数宣言
private IMasterObj[] masterObjArray;

// オブジェクト取得
var masterObjArray = InterfaceUtils.FindObjectOfInterfaces<IMasterObject>();

 

実装コード

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Interface便利クラス
/// </summary>
public class InterfaceUtils
{
    /// <summary>
    /// 特定のインタフェースがアタッチされたオブジェクトを見つける
    /// </summary>
    /// <typeparam name="T"> 探索するインタフェース </typeparam>
    /// <returns> 取得したクラス配列 </returns>
    public static T[] FindObjectOfInterfaces<T>() where T : class
    {
        List<T> findList = new List<T>();

        // オブジェクトを探索し、リストに格納
        foreach (var component in GameObject.FindObjectsOfType<Component>())
        {
            var obj = component as T;

            if (obj == null) continue;

            findList.Add(obj);
        }

        T[] findObjArray = new T[findList.Count];
        int count = 0;

        // 取得したオブジェクトを指定したインタフェース型配列に格納
        foreach (T obj in findList)
        {
            findObjArray[count] = obj;
            count++;
        }
        return findObjArray;
    }
}

終わりに

 使う場面は限られるかもしれませんが、ある程度の一括管理などをしている場合には、結構いい感じに使えるんじゃないかなと思います。
 私の場合は特にUpdateも、なるべくUnity本来のコールバックを使用しない形での実装を目指しているので、重宝しています。


 まだ改善できる箇所があるような気がしているので、よりよい実装があれば教えて頂けるとありがたいです。


 それでは、今回はこの辺りで。

Unityゲーム開発で役立つインタフェース活用例


 こんにちは、ここあです。

 今回は、私がUnityでゲーム開発をする際によく使う、インタフェースの実装を書いていきたいと思います。

インタフェースとは

実装の強制

 簡潔に行ってしまえば、インタフェース機能の実装を強制することが出来る仕組みです。
 これは別の言い方をすると、インタフェースに定義された属性やメソッドは、確実に継承された先のクラスに実装されていることが保証されます

処理の窓口

 実装に関しては、インタフェースを引き継いだクラス側で自由に変更することができ、呼び出す側はその実装内容に関して気にすることなく使えます。
 この事から、インタフェースは処理の窓口という特性も持っています。


 ちなみに、クラスなどに、インタフェースを引き継ぐことを、継承ではなく、実装すると言います。

Update関数を自前の実装で回す

コールバック関数は重い

 UnityでC#を書く場合、多くの方がUpdate関数をそのまま各スクリプトに書いて実行しているのではないかと思います。
 少なくとも、初学者の方々はそうだと思います。実際、私がそうでした。

 それ自体、大きく問題にすることではありませんが、最適化を求めたり、処理の効率を気にするようになってくると少し話は変わってきます。


 Update関数に限らず、Unityの提供しているコールバック関数はちょくちょく重いです。


 弾幕シューティングの様に、大量のオブジェクトが出現する場合や、千個単位もしくはそれ以上のオブジェクト全てにUpdate関数が付いていると、処理効率を求めるうえで、あまり無視して良い処理量では無くなってきます。

 では、どうやって処理を軽くするか。


 単純明快。自前の関数を呼び出すことで、コールバック関数の重さを回避することが出来ます。

実装方法

 私の場合、次の様な実装の形式を取る事が多いです。

 f:id:holmes8901:20181225194100p:plain

 MasterManagerがインタフェースを経由して、それを実装した全てのScriptのUpdateを動かす形です。
 これのメリットは、先ほど述べた通り、コールバック関数を使わない。
 つまり、各スクリプトに継承されるMonoBehaviourを最小限に抑えることが出来る点です。

実装コード

using UnityEngine;

[DisallowMultipleComponent]
public class MasterManager : MonoBehaviour
{
    private IUpdateByFrame[]  m_updateByFrame;

    /// <summary>
    /// 各Managerの初期取得処理
    /// </summary>
    private void Awake()
    {
        m_masterObject = FindObjectOfInterface.FindObjectOfInterfaces<IUpdateByFrame>();
    }

    /// <summary>
    /// 各ManagerのUpdateByFrame関数をここから回す
    /// </summary>
    private void Update()
    {
       // 取得したMasterScene内オブジェクトのUpdateを回す
        foreach (var list in m_masterObject)
        {
            // インタフェースを継承しているか確認
            if (list is IUpdateByFrame update)
            {
                update.UpdateByFrame();
            }
        }
    }
}

FindObjectOfInterface.FindObjectOfInterfacesは自分で実装した、Interfaceを持ったオブジェクトを取得する為の拡張メソッドになります。
これについては、今回は説明しません。

Scene内オブジェクトの管理に空のインタフェース

各シーン内に存在するオブジェクト管理

 もうひとつよく行っているのが、空インタフェースの実装です。

 これをどう使うかというと、私は各シーン内に存在する管理クラスを捕まえてくるのに使います。

 私はマスターシーンを一つ用意して、そこに追加で各シーンをロードする方法を取っています。
 マスタシーンでIUpdateByFrameを実装したオブジェクトを取得する命令が間違って走ってしまった場合、全てのシーンからインタフェースを実装したオブジェクトを取得してしまいます。
 それを回避するために、使います。

実装方法

 今回の場合、次のようなアクセスの流れになります。

 f:id:holmes8901:20181225211553p:plain

 MasterManagerはIMasterObjを実装したMasterScene内の管理クラスを取得する。
 SceneManagerはInGameManagerを持っており、InGameManagerはIInGameObjを実装したInGameScene内の管理クラスを取得してくるといった形です。

 こうすることで、各シーンを束ねる管理クラスが、適切に自身の管理領域に存在するクラスを取得することが出来ます。

実装コード

 MasterScene上記で記載したIUpdateByFrameの使用と組み合わせると、次の様な実装になります。
 これは、各管理クラスが各シーン用インタフェースと、IUpdateByFrameを実装しているのが前提となります。

[DisallowMultipleComponent]
public class MasterManager : MonoBehaviour
{
    private IMasterObject[]  m_masterObject;

    /// <summary>
    /// 各Managerの初期取得処理
    /// </summary>
    private void Awake()
    {
        m_masterObject = FindObjectOfInterface.FindObjectOfInterfaces<IMasterObject>();
    }

    /// <summary>
    /// 各ManagerのUpdateByFrame関数をここから回す
    /// </summary>
    void Update()
    {
        // 取得したMasterScene内オブジェクトのUpdateを回す
        foreach (var list in m_masterObject)
        {
            // インタフェースを継承しているか確認
            if (list is IUpdateByFrame update)
            {
                update.UpdateByFrame();
            }
        }
    }
}

終わりに

 今回紹介した方法は、Interfaceの使用法としてあまりネットに情報が出てない気がしたので紹介しました。
 またこの方法は、本当に処理効率を考えるのであれば、キャストがそこそこ入る関係上、少し遅くなる可能性もあります。
 その辺の検証については、実仕様で問題が出ていなかったので、まだ行っていませんでした。

 組み方次第だとは思いますが、その点については留意しておいてください。

 また、今回の内容に関して、私の間違った認識やより良い方法があれば教えて頂けるとありがたいです。
 
 それでは、今回はこのあたりで。

【Unity】VisualEffectGraph導入方法


こんにちは、ここあです。

ここ最近疲れているのか、気絶するように寝てしまっていました。
DirectX12の続きを投稿しようかと思ったのですが、ベータリリースされている、Unity2018.3で使うことの出来るVisualEffectGraphを導入して少し触ってみたので、導入用の記事を書こうと思います。

VisualEffectGraphとは

従来のパーティクルはCPUで制御していましたが、現在BetaReleaseされているUnity2018.3.0f1では、VisualEffectGraphを使うことによって、数百万単位のパーティクルをGPUで制御できるようになりました。

VisualEffectGraphを使用するためには、Unity上でPackageManagerを利用して導入する必要があります。

今回動作を確認した環境

 今回、私が導入したのはUnity2018.3.0b12ですが、現在最新のUnity2018.3.0f1でも、同様の方法で導入が可能なことを確認しています。

使用したバージョン

 Unity2018.3.0b12

導入したPackage

 Core RP Library
 High Definition RP
 Visual Effect Graph

導入手順

0. 上記Packageの導入

Windowタブ
└ Package Manager
 └各Package ← Install
f:id:holmes8901:20181210232501p:plain
f:id:holmes8901:20181210234114p:plain

1. Scene Setting の作成

Hierarchy
 └Create
  └Rendering
   └Scene Settingを作る
f:id:holmes8901:20181210232514p:plain

2. High Definition Render Pipeline Asset の作成

Project
 └Create
  └Rendering
   └High Definition Render Pipeline Assetを作る
f:id:holmes8901:20181210232521p:plain

3. Graphicsの設定

Edit
 └Project Settings
  └Graphics
  └ScriptableRender Pipeline Settings ← 作成したHDRPAssetを入れる
f:id:holmes8901:20181210232509p:plain
f:id:holmes8901:20181210233801p:plain

4. VisualEffectGraphで遊ぼう

 Hierarchy
  └VisualEffects
   └VisualEffect
f:id:holmes8901:20181210234517p:plain

 これで準備はできました。
 あとは思う存分VisualEffectGraphで遊び倒しましょう!

終わりに

 以前のバージョンでは、GitHubからクローンして来なければならなかったのですが、パッケージマネージャーを通して導入することが出来るようになった為、かなり楽に導入することが出来るようになりました。
 今までのパーティクルと違い、ノードを組む形で作っていくので、慣れるまでは大変だと思いますが、各ノードも割とわかりやすい名前が付けられているので、使いやすい印象が強かったです。

 これで一段高いレベルのゲームを作れるようになったはず……!
 ということで、DirectX12もやりつつ、Unityのほうでもちょっと作りたいものが出来たので、作っていきたいなと思います。
 それでは、今回はこのあたりで。
 

ゲームを作る際に私が気を付けていること《企画編》

 

 こんにちは、ここあです。

 

 少し期間が開いてしまいました。。。

 今回は少しDirectXから離れて、ゲームを作る際に私が気を付けていることについて、書いていきたいと思います。

 

 今回の内容は、人から聞かせていただいたお話を、私なりに解釈した結果のものです。お話を聞いた方の考えからは、変わっている可能性もある、私個人の考えになります。

 

ゲーム制作時に気を付けていること

企画

 ゲーム制作初期――ゲームを作る際の立ち上げ段階といえば、どんなゲームを作るのか、企画を決めなければなりませんよね。

 

 そこで考えるのは、これから作るゲームは、プレイして頂くユーザー。つまりは、お客様に楽しんでもらうことが出来るのかということです。

 学生なので、私は学校の代表作として、TGSにゲームを出展させていただきましたが、このゲームを作る際には、開発の最初から最後までチームメンバー達と話し合い、意見を交わしながら、「より楽しんでもらえるモノを!」という意識をもって、開発に臨みました。

 

 私は、企画を練るにあたって、以前参加させていただいた、DeNAインターンで次のことを学ぶことが出来ました。

 

面白いの言語化

 

 この一言で、企画の面白いの全てが決まるのでは無いか、というくらいに自分の中で衝撃が走りました。

 実際、この言語化をすることで、誰でも納得のできるものにすることが出来るとおもします。だからこそ、「遊ぶ人に長く楽しんでもらえるんだ」と。

 これを考えて、ゲームとして楽しんでもらえない場合は、自分の言語化が甘かったか、趣味嗜好の範囲だと思います。

 

 最近友人と話した内容から抜粋しますが、例を出すとすれば音ゲーについてです。

  ・全てに共通して言えることは音がある

  ・音に対して、自分がリアクションを取る

 このことから、私は次のように考えます。

 

「小さな成功体験の連続性」かなと私は思っています。

 

「音に合わせてちゃんと押すことが出来た」

「それに対する反応として効果音が聞こえた」

 

 この小さな成功体験を連続で休むことなく続けることこそが、音ゲーの楽しさの根底にあると思います。

 

 この先に待っているのが、今までクリアできなかった、高い難易度の譜面をクリアした際の達成感だと思います。

 実際このクリアという達成感も、大事ではありますが、小さな成功体験を積み重ねることによって、「今までの譜面はミスっていなかったし、次こそは上手く出来るのではないか」という思考を呼び起こすことで、自然と次のプレイに手が伸びる。そんな楽しさをユーザーの方に感じて頂けると考えています。

 

 

終わりに

 さて、ここまで私の考えを書き連ねてきましたが、要はユーザーに楽しんでほしいし、自分もゲームを作るのが楽しいからゲームを作っています

 

 最近はDirectXばかり勉強している私ですが、もともとはファンタジーが好きなゲームユーザーです。今も好きですし、体験できるのであれば、

「現実では起こり得ないような非日常的なアレコレを是非とも体験してみたい!」

 

 そんな私なので、これからもゲームを作っていきますので、出来た際には、是非よろしくお願い致します。

【DirectX12】コマンドアロケータの作成【初期化】


 こんにちは、ここあです。
 今回はコマンドアロケータの作成と、コマンドアロケータで使うことの出来るBundleについて解説をしていきます。

Library & Includes

#include <d3dx12.h>
#pragma comment(lib, "d3d12.lib")
#include <dxgi1_4.h>
#pragma comment(lib, "dxgi.lib")
#include <D3Dcompiler.h>
#pragma comment(lib, "d3dcompiler.lib")
#include <DirectXMath.h>

using Microsoft::WRL::ComPtr;
using namespace DirectX;

コマンドアロケータの作成

コマンドアロケータとは

 コマンドアロケータは、コマンドリストに格納する命令の為のメモリを管理するオブジェクトです。
 
 命令の割り当てられたメモリは、その命令の実行が終わるまで開放することが出来ません
 コマンドリスト単位で見ると「命令の実行が終わる」のは、基本的に「フレームバッファへの描画が完了」したことを指します。
 つまり描画中のメモリは、命令の実行が終わっていない為、コマンドリストの作成に使えません
 なので通常は、最低でもフレームバッファと同じ数だけ、コマンドアロケータの作成をします。

CreateCommandAllocator関数

 コマンドアロケータの作成には、ID3D12Device::CreateCommandAllocator関数を使います。

ID3D12Device::CreateCommandAllocator method | Microsoft Docs

  • CreateCommandAllocator関数
    • 第1引数:コマンドアロケータの種類
    • 第2引数:各インタフェース固有のGUID
    • 第3引数:ID3D12CommandAllocatorインタフェースのポインタを格納する変数のアドレス
  • 補足説明
    • 第1引数:コマンドリストと違い、BUNDLEを含めた全ての種類を指定可能

第2、第3引数は、IID_PPV_ARGSマクロを使うことで簡易的に受け渡しが可能。
 

事前定義
const UINT FRAME_COUNT = 2;

ComPtr<ID3D12Device> g_device;
ComPtr<ID3D12CommandAllocator> g_commandAllocator[FRAME_COUNT]
実装
for (int i = 0; i < FRAME_COUNT ; ++i) {
    if (FAILED(g_device->CreateCommandAllocator(
        D3D12_COMMAND_LIST_TYPE_DIRECT,
        IID_PPV_ARGS(&g_commandAllocator[i])))) {
        return false;
    }
}

D3D12_COMMAND_LIST_TYPE_BUNDLE

Bundleとは

 日本語で言うと、束。
 その名の通り、これは小さなコマンドリストを表します。
 Bundleには、事前にコマンドを積んでおくことが出来て、コマンドリストから何度も使用可能です。

Bundleを使う際の注意点

  • Bundleによって参照されるすべてのパイプラインステートオブジェクトは、以下のそれぞれが同一でなければならない
    • レンダーターゲットのフォーマット
    • 深度バッファのフォーマット
    • サンプル
  • D3D12_COMMAND_LIST_TYPE_BUNDLE で生成されたコマンドリスト上で、以下のコマンドリストAPIの呼び出しは許可されない
    • すべての Clear メソッド
    • すべての Copy メソッド
    • DiscardResource
    • ExecuteBundle
    • ReosolveSubresource
    • SetPredication
    • BeginQuery
    • EndQuery
    • SOSetTargets
    • OMSetRenderTargets
    • RSSetViewports
    • RSSetScissorRects
  • SetDescriptorHeap は、Bundle上で呼び出し可能だが、バンドルディスクリプタヒープ呼び出し元のコマンドリストディスクリプタヒープと一致する必要がある。


 これらのAPIがコールされた場合、ランタイムはコールを取りやめるらしいので、使う際は注意しましょう。


終わりに

 お疲れ様でした。
 コマンドアロケータで使用できるBundleについても、少し詳しく解説をしました。
 DirectX12で新しく追加された機能なので、私も勉強してちゃんと使えるようになる予定です。

 解説が間違っていたり、より良い方法などがあった場合は教えて頂けるとありがたいです。

 次回はコマンドリストの作成について解説していきます。


 それでは、今回はこの辺りで。

github.com

【DirectX12】レンダーターゲット用デスクプタの作成【初期化】


 こんにちは、ここあです。

 少し更新が開いてしまいましたが、今日はレンダーターゲット用デスクプタの作成をやっていきたいと思います。


Library & Includes

#include <d3dx12.h>
#pragma comment(lib, "d3d12.lib")
#include <dxgi1_4.h>
#pragma comment(lib, "dxgi.lib")
#include <D3Dcompiler.h>
#pragma comment(lib, "d3dcompiler.lib")
#include <DirectXMath.h>

using Microsoft::WRL::ComPtr;
using namespace DirectX;

レンダーターゲット用デスクリプタの作成

デスクリプタとは

 デスクリプタヒープというのは、GPU上に作られるデスクリプタを保存するための配列です。
 前回、説明したので、詳しい説明は省略します。

デスクリプタヒープの先頭アドレスを保持するハンドルの取得

 まず初めに、CPU向けにデスクリプタヒープの先頭アドレスを保持するハンドルを取得します。
 これは、GPUメモリ上のデスクリプタヒープに、CPUから値を設定したい為に行います。

CPU向けのアドレス取得はなぜ?

 前回、デスクリプタヒープはGPU上に作成されるという説明をしました。
 それなのに、CPU向けのアドレスを取得するのはなぜか
 それはGPUメモリには、GPU向けのアドレスと、CPU向けのアドレスが存在するからです。

 GPUメモリにはGPUだけではなく、CPUからも読み込みと書き込みができます。
 ですが、CPUとGPU異なるメモリ管理装置を経由してメモリを見ている場合があり、その場合にはGPUメモリの見え方は異なるのです。

メモリを参照する番地管理の違い

 メインメモリとGPUメモリを持ったコンピューターを、単純な例として考えましょう。

 GPUGPUメモリだけ見えていれば十分です。なのでGPUGPUメモリをアドレス0番から数えます。
 ですが、CPUにとってはGPUメモリより、メインメモリが重要です。なのでCPUは、メインメモリをアドレス0番から。GPUメモリをメインメモリの最後の番地から逆順に数えます。

 この時点でCPUとGPUの間で、メモリの数え方が異なるものになってしまいました。


 実際は、いろいろな理由はありますが、最も端的な理由として、このような現象が起きる為、GPUメモリにはGPU向けのアドレスと、CPU向け、2つのアドレスが存在します。


レンダーターゲットはフレームバッファと1:1となるように作成

 アドレスを取得したので、作成の為にフレームバッファの数だけforループを回します。

 IDXGISwapChain1::GetBuffer関数を使って、i番目のレンダーターゲットのID3D12Resouceインターフェイスを取得し、ID3D12Device::CreateRenderTargetView関数によって、デスクリプタヒープ上にRTV用のデスクリプタを作成しています。

 最後に、次のデスクリプタのアドレスを計算して、レンダーターゲット用デスクプタの作成は終わりです。

ID3D12Resouce

 GPUメモリにある、様々なデータを管理するための汎用クラスです。
 配列、テクスチャ、モデルといったデータは、全てID3D12Resouceインターフェイスを通して管理されます。
f:id:holmes8901:20181121102440p:plain

CreateRenderTargetView関数

 この関数を使用することで、デスクリプタヒープ上にRTV用のデスクリプタを作成できます。

ID3D12Device::CreateRenderTargetView method | Microsoft Docs

  • CreateSwapChainForHwnd関数
    • 第1引数:ID3D12Resouceインターフェイスへのポインタ
    • 第2引数:D3D12_RENDER_TARGET_VIEW_DESC構造体へのポインタ
    • 第3引数:D3D12_CPU_DESCRIPTOR_HANDLE型のアドレス
  • 補足説明
    • 第1引数:デスクリプタに割り当てるレンダーターゲット
    • 第2引数:nullptrを渡すことで、デフォルト値で初期化した構造体のポインタが渡されたかのように動作
    • 第3引数:デスクリプタを作成するアドレス

D3D12_RENDER_TARGET_VIEW_DESC構造体

 CreateRenderTargetView関数の第2引数として渡す、D3D12_RENDER_TARGET_VIEW_DESC構造体には、二つのメンバがあります。

D3D12_RENDER_TARGET_VIEW_DESC | Microsoft Docs

  • D3D12_RENDER_TARGET_VIEW_DESC
    • Format:表示形式を指定
    • ViewDimension:レンダーターゲットのリソースにアクセスする方法を指定
事前定義
const UINT FRAME_COUNT = 2;

UINT g_rtvDescriptorSize;

ConPtr<ID3D12Device>  g_device
ConPtr<ID3D12SwapChain3> g_swapChain;
ConPtr<ID3D12Resource> g_renderTargfets[FRAME_COUNT];
実装
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = {};
rtvHandle.ptr = rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart();

// フレームバッファの数だけループ
for (int i = 0; i < FRAME_COUNT; ++i) {
    
    // バッファを取得
    if (FAILED(g_swapChain->GetBuffer( i, IID_PPV_ARGS(&g_renderTargfets[i])))) {
        return false;
    }

    // レンダーターゲットビュー
    g_device->CreateRenderTargetView( g_renderTargfets[i].Get(), nullptr, rtvHandle);
    rtvHandle.ptr += g_rtvDescriptorSize;
}

終わりに

 お疲れ様でした。今回で初期化処理のほとんどが終わりました。
 
 今回の実装内容はGitHubに上げてあります。
 解説が間違っていたり、より良い方法などがあった場合は教えて頂けるとありがたいです。

 あと残すのは、コマンドアロケータ、コマンドリストの作成と、フェンス、フェンスイベントの作成です。
 次回はコマンドアロケータの作成について解説していきます。


 それでは、今回はこの辺りで。

github.com

【DirectX12】デスクリプタヒープの作成【初期化】


 こんにちは、家に帰ってすぐにベッドインしてしまった、ここあです。
 少し疲れ気味だったのか、久しぶりにゆっくりと寝ました。(寝すぎた)
 更新が少し空いてしまいましたが休めたということは頑張れるので、今日からまた頑張っていきたいと思います。

 今回は、レンダーターゲット用のデスクリプタヒープの作成について、解説していきます。

デスクリプタヒープの作成

デスクリプタヒープとは

 デスクリプタヒープというのは、GPU上に作られるデスクリプタを保存するための配列です。

f:id:holmes8901:20181121102440p:plain
Direct3D 12 Overview Part 4: Heaps and Tables | Intel® Software

 上の図では、デスクリプタヒープには、緑色のデスクリプタが配列の中に入っています
 そして、それぞれのデスクリプタが、テクスチャやモデル、バッファ、何らかのデータ配列が入っているのが、表されています。
 

デスクリプタとは

 GPUメモリ上に存在する、様々なデータやバッファの種類や位置、大きさを示す構造体のようなものです。
 

f:id:holmes8901:20181121102454p:plain
Direct3D 12 Overview Part 4: Heaps and Tables | Intel® Software

 上の図では、デスクリプタには、テクスチャの種類やフォーマット、ミップマップ数、GPUメモリ上のテクスチャへのポインタなどの、各種データが格納されています

CreateDescriptorHeap関数

 デスクリプタヒープの作成にはID3D12Device::CreateDescriptorHeap関数を使用します。

  • CreateDescriptorHeap関数
    • 第1引数:D3D12_DESCRIPTOR_HEAP_DESC構造体へのポインタ
    • 第2引数:各インタフェースの固有GUID
    • 第3引数:ID3D12DescriptorHeapインタフェースのポインタを格納する変数のアドレス

ID3D12Device::CreateDescriptorHeap method | Microsoft Docs


 基本的に今までと同様GUIDやインタフェースを格納した変数のアドレスに関しては、IID_PPV_ARGSマクロを使用することで、簡易的に指定することが可能です。

D3D12_DESCRIPTOR_HEAP_DESC構造体

  • D3D12_DESCRIPTOR_HEAP_DESC構造体
    • Type:デスクリプタヒープのタイプを設定。使いみちに応じて4つのタイプから設定
      • CBV/SRV/UAV :配列やモデル、テクスチャ等のデータ用
      • SAMPLER:テクスチャ読み込みのパラメータ用
      • RTVレンダーターゲットを指定 ← 今回作成
      • DSV:深度やステンシルマスクの描画先を指定
    • NumDescriptors格納できるデスクリプタの数(デスクリプタヒープが配列の為、配列数の指定が必要)。通常レンダーターゲットはフレームバッファと1:1になるように作成する
    • FlagsD3D12_DESCRIPTOR_HEAP_FLAG_SHADER_NONEを設定。デスクリプタが指しているデータをシェーダから参照したい場合はD3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLEを設定。
    • NodeMask:複数のGPUを使用する場合、どのGPU向けのデスクリプタヒープかを指定GPUを1つしか使用しない場合、0を設定

D3D12_DESCRIPTOR_HEAP_DESC | Microsoft Docs


※CBV:Constant Buffer View(配列等)
SRV:Shader Resource View(テクスチャ、フレームバッファ、モデル等)
※UAV:Unordered Access View(SRVと同じですが、シェーダから直接書き込める点が違います)

※RTV:レンダーターゲットビューの略称
※DSV:Depth Stencil Viewの略称

※Flags:現状、意味のある値として設定できるのは、D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLEしかありません。今回は何も指定しないためNONEを設定しています

レンダーターゲットビュー用デスクリプタのバイト数を取得

 ID3D12Device::GetDescriptorHandleIncrementSize関数を使用して、RTV用デスクリプタのバイト数を取得します。
 デスクリプタヒープはデスクリプタの配列と説明しましたが、C/C++などの配列とは異なり、ヒープ配列は添字で参照することが出来ません。では、N番目の要素を参照したいといった場合に、どうやって参照するかというと、下のように計算をして参照します。

N番目の要素のアドレス=デスクリプタヒープの先頭アドレス+(要素のバイト数*N)

 この計算式から分かるように、デスクリプタのバイト数が必要になってきます。このバイト数は、D3Dデバイスによって異なる可能性があるのでこのバイト数をD3Dデバイスから取得してきます。

ID3D12Device::GetDescriptorHandleIncrementSize method | Microsoft Docs

事前定義
ComPtr<ID3D12DescriptorHeap>		g_rtvHeap;
const UINT FRAME_COUNT = 2;
実装
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = FRAME_COUNT;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
if (FAILED(resource->g_device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&g_rtvHeap)))){
    return FALSE;
}

resource->g_rtvDescriptorSize = g_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);

終わりに

 お疲れ様でした。今回でデスクリプタを格納するための準備が出来たので、次回はデスクリプタの作成をしていきます。

 今回の実装内容はGitHubに上げてあります。
 解説が間違っていたり、より良い方法などがあった場合は教えて頂けるとありがたいです。

 それでは今回はこの辺りで。

github.com