SUU-Lab

プログラムに関するメモとかが多いです。

Direct3D11初期化&画面クリア 続き

間が空いてしまいましたが、前回の続きを書いていきたいと思います。

画面クリア

画面クリアの手順を見ていきましょう。
 

1.バックバッファのリサイズ処理

IDXGISwapChain::ResizeBuffers 関数でクライアント領域に合わせて、
バックバッファのリサイズを行っています。

// SampleApp::CreateBackBuffer(const Size2D& newSize)
    // バッファのサイズを変更
    HRESULT hr = m_SwapChain->ResizeBuffers(
        m_BufferCount,
        static_cast<UINT>(newSize.width),
        static_cast<UINT>(newSize.height),
        m_BufferFormat,
        DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH
    );
    if (FAILED(hr))
    {
        return false;
    }

 

IDXGISwapChain::ResizeBuffers 関数

HRESULT ResizeBuffers(
UINT BufferCount,
UINT Width,
UINT Height,
DXGI_FORMAT NewFormat,
UINT SwapChainFlags
);

BufferCount
 スワップチェインのバッファ数です。

 作成した時と同じ値を指定しておけば問題ないでしょう。

Width
 バック バッファーの新しい幅です。

 0 が指定された場合、ターゲット ウィンドウのクライアント領域の幅が使用されます。

Height
 バック バッファーの新しい高さです。

 0 が指定された場合、ターゲット ウィンドウのクライアント領域の高さが使用されます。

NewFormat
 バック バッファーの新しいフォーマットです。

 DXGI_FORMAT の値を指定できます。

 変更しない場合は作成時と同じ値を指定します。

SwapChainFlags
 スワップチェインの動作オプションです。

 DXGI_SWAP_CHAIN_FLAG の値を指定できます。

 変更しない場合は作成時と同じ値を指定します。
 

2.バックバッファを取得

IDXGISwapChain::GetBuffer 関数でバックバッファを取得します。

// SampleApp::CreateBackBuffer(const Size2D& newSize)
    ComPtr<ID3D11Texture2D> backBuffer;

    hr= m_SwapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer));
    if (FAILED(hr))
    {
        return false;
    }

 

IDXGISwapChain::GetBuffer 関数

HRESULT GetBuffer(
UINT Buffer,
REFIID riid,
void **ppSurface
);

Buffer
 取得したいバッファーのインデックスです。

 レンダーターゲットを作成する場合は 0 で問題ないでしょう。

riid
 バッファーの操作に使用するインターフェイスの種類です。

ppSurface
 バック バッファー インターフェイスへのポインターです。
 
※引数の指定の仕方について
IID_PPV_ARGS マクロを使用すると簡潔に記述できます。

m_SwapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer));

 

3.レンダーターゲットを作成

ID3D11Device::CreateRenderTargetView 関数でレンダーターゲットを作成します。

// SampleApp::CreateBackBuffer(const Size2D& newSize)
    hr = m_Device->CreateRenderTargetView(
        backBuffer.Get(),
        nullptr,
        &m_RenderTargetView
    );
    if (FAILED(hr))
    {
        return false;
    }

 

ID3D11Device::CreateRenderTargetView 関数

HRESULT CreateRenderTargetView(
ID3D11Resource *pResource,
const D3D11_RENDER_TARGET_VIEW_DESC *pDesc,
ID3D11RenderTargetView **ppRTView
);

pResource
 レンダー ターゲットを表す ID3D11Resource へのポインターです。

 このリソースは、D3D11_BIND_RENDER_TARGET フラグを使用してあらかじめ作成しておく必要があります。

 ここでは、IDXGISwapChain::GetBuffer 関数で取得した値を指定しています。

pDesc
 レンダー ターゲット ビューの記述を表す D3D11_RENDER_TARGET_VIEW_DESC へのポインターです。

 バックバッファのレンダーターゲットの場合 NULL を指定できます。

ppRTView
 作成されたレンダーターゲットを表す ID3D11RenderTargetView へのポインターのアドレスです。
 

4.レンダーターゲットを設定

ID3D11DeviceContext::OMSetRenderTargets 関数を使用して設定します。

// SampleApp::CreateBackBuffer(const Size2D& newSize)
    ID3D11RenderTargetView* pRenderTargetViews[] = {
        m_RenderTargetView.Get()
    };
    m_Context->OMSetRenderTargets(
        _countof(pRenderTargetViews),
        pRenderTargetViews,
        nullptr
    );

 

ID3D11DeviceContext::OMSetRenderTargets 関数

void OMSetRenderTargets(
UINT NumViews,
ID3D11RenderTargetView *const *ppRenderTargetViews,
ID3D11DepthStencilView *pDepthStencilView
);

NumViews
 バインドするレンダー ターゲットの数です (範囲は 0 ~ D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT)。

 ppRenderTargetViews の要素数です。

ppRenderTargetViews
 デバイスにバインドするレンダー ターゲットの配列へのポインターです。

 このパラメーターが NULL の場合、レンダー ターゲットはバインドされません。

pDepthStencilView
 デバイスにバインドする深度ステンシル ビューへのポインターです。

 このパラメーターが NULL の場合、深度ステンシル ステートはバインドされません。
 

5.ビューポートの設定

D3D11_VIEWPORT 構造体で設定を記述し、
ID3D11DeviceContext::RSSetViewports 関数で設定します。

// SampleApp::CreateBackBuffer(const Size2D& newSize)
    D3D11_VIEWPORT viewport;
    viewport.Width = static_cast<FLOAT>(newSize.width);
    viewport.Height = static_cast<FLOAT>(newSize.height);
    viewport.MinDepth = 0.0f;
    viewport.MaxDepth = 1.0f;
    viewport.TopLeftX = 0.0f;
    viewport.TopLeftY = 0.0f;
    m_Context->RSSetViewports(1, &viewport);

 

6.画面クリア

ID3D11DeviceContext::ClearRenderTargetView 関数を使用して画面をクリアします。

// SampleApp::Render()
    // 指定色でクリア
    FLOAT clearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; // red, green, blue, alpha
    m_Context->ClearRenderTargetView(m_RenderTargetView.Get(), clearColor);

 

7.バックバッファを画面に反映

IDXGISwapChain::Present 関数でバックバッファを画面に反映します。

// SampleApp::Render()
    // 結果をウインドウに反映
    HRESULT hr = m_SwapChain->Present(0, 0);
    if (FAILED(hr))
        // Error!
    }

 
最後の方が説明不足な気がしますが、これで画面クリアができました。
長かったです。

f:id:n-suudai:20180505135844p:plain
画面クリア

Direct3D11初期化&画面クリア

Direct3D11 を使用して初期化&画面クリアするアプリケーションを作成しました。

サンプルアプリケーションクラス

初期化&画面クリアに必要な最小クラスです。

// SampleApp.hpp

#pragma once

#include "IApp.hpp"
#include <dxgi.h>
#include <d3d11.h>
#include <wrl.h>

template<class T>
using ComPtr = Microsoft::WRL::ComPtr<T>;

class SampleApp
{
public:
    SampleApp(IApp* pApp);

    ~SampleApp();

    // 初期化
    bool Init();

    // 描画処理
    void Render();

    // リサイズ
    void OnResize(const Size2D& newSize);

private:
    // バックバッファを作成
    bool CreateBackBuffer(const Size2D& newSize);

private:
    IApp * m_pApp;
    UINT m_BufferCount;

    DXGI_FORMAT       m_BufferFormat;
    D3D_FEATURE_LEVEL m_FeatureLevel;

    ComPtr<IDXGIFactory>   m_Factory;
    ComPtr<IDXGISwapChain> m_SwapChain;

    ComPtr<ID3D11Device>           m_Device;
    ComPtr<ID3D11DeviceContext>    m_Context;
    ComPtr<ID3D11RenderTargetView> m_RenderTargetView;
};

 

Direct3D11 初期化

初期化の手順を見ていきましょう。

1.ライブラリのリンク

Direct3D11 を利用するのに必要なライブラリをリンクしています。

// SampleApp.cpp

// DXGI & D3D11 のライブラリをリンク
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")

 

2.IDXGIFactory の作成

IDXGISwapChain を作成するのに必要なので、あらかじめ作成しておきます。

CreateDXGIFactory 関数で作成します。

// SampleApp::Init()
HRESULT hr = CreateDXGIFactory(IID_PPV_ARGS(&m_Factory));
if(FAILED(hr))
{
    return false;
}

 

3.ID3D11Device & ID3D11DeviceContext の作成

D3D11CreateDevice 関数で作成しています。

IDXGISwapChain と同時に作成も可能なのですが、今回は別々に作成しています。

// SampleApp::Init()

#if defined(DEBUG) || defined(_DEBUG)
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    D3D_FEATURE_LEVEL featureLevels[] = {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
    };

    // デバイス&コンテキストを生成
    hr = D3D11CreateDevice(
        nullptr,
        D3D_DRIVER_TYPE_HARDWARE, // ハードウェア ドライバー を使用
        nullptr,
        createDeviceFlags,
        featureLevels,
        _countof(featureLevels),
        D3D11_SDK_VERSION,
        &m_Device,
        &m_FeatureLevel,
        &m_Context
    );
    if (FAILED(hr))
    {
        return false;
    }

 

D3D11CreateDevice 関数

HRESULT D3D11CreateDevice(
IDXGIAdapter *pAdapter,
D3D_DRIVER_TYPE DriverType,
HMODULE Software,
UINT Flags,
CONST D3D_FEATURE_LEVEL *pFeatureLevels,
UINT FeatureLevels,
UINT SDKVersion,
ID3D11Device **ppDevice,
D3D_FEATURE_LEVEL *pFeatureLevel,
ID3D11DeviceContext **ppImmediateContext
);

pAdapter
 デバイスの作成時に使用するビデオ アダプターへのポインターです。

 通常は既定のアダプターで良いので、NULL で問題ないです。

DriverType
 作成するデバイスの種類です。

 D3D_DRIVER_TYPE のいずれかを指定します。

 pAdapter に NULL 以外の値を指定する場合は、 D3D_DRIVER_TYPE_UNKNOWN を指定する必要があります。

 通常は D3D_DRIVER_TYPE_HARDWARE 問題ないでしょう。

Software
 ソフトウェア ラスタライザーを実装する DLL のハンドルです。

 DriverType に D3D_DRIVER_TYPE_SOFTWARE を指定した場合、NULL に設定することはできません。

 D3D_DRIVER_TYPE_SOFTWARE を指定することはほぼないので、NULL で問題ないでしょう。

Flags
 有効にするランタイム レイヤーです。

 D3D11_CREATE_DEVICE_FLAG の値を OR 演算で指定できます。

 デバッグレイヤーを有効にするには、D3D11_CREATE_DEVICE_DEBUG を指定します。

 デフォルトで良い場合は 0 で構いません。

pFeatureLevels
 作成を試みる機能レベルの順序を指定する配列へのポインターです。

 機能レベルが高いものから順に並べておくと、最大の機能レベルを取得できます。

FeatureLevels
 pFeatureLevels の要素数です。

SDKVersion
 SDK のバージョンです。D3D11_SDK_VERSION を指定します。

ppDevice
 作成されたデバイスを表す ID3D11Device オブジェクトへのポインターのアドレスを返します。

pFeatureLevel
 成功した場合は、成功した pFeatureLevels 配列の最初の D3D_FEATURE_LEVEL を返します。

 失敗した場合は 0 を返します。

ppImmediateContext
 デバイス コンテキストを表す ID3D11DeviceContext オブジェクトへのポインターのアドレスを返します。

4.MSAA (Multi-Sample Anti-Aliasing) の設定

今回は画面クリアまでなので必要ないかもしれませんが、一応設定だけしておきます。

MSAA については、ここでは説明を省きます。

スワップチェインの作成時に MSAA を有効化できるのですが、その設定に利用する値を調べます。

ID3D11::CheckMultisampleQualityLevels 関数を使って、利用可能なサンプリングカウントと品質の最大値を調べます。

// SampleApp::Init()
    // 使用可能なMSAAを取得
    DXGI_SAMPLE_DESC sampleDesc;
    ZeroMemory(&sampleDesc, sizeof(sampleDesc));
    for (int i = 1; i <= D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT; i <<= 1)
    {
        UINT Quality;
        if (SUCCEEDED(m_Device->CheckMultisampleQualityLevels(DXGI_FORMAT_D24_UNORM_S8_UINT, i, &Quality)))
        {
            if (0 < Quality)
            {
                sampleDesc.Count = i;
                sampleDesc.Quality = Quality - 1;
            }
        }
    }

 

5.DXGI_SWAP_CHAIN_DESC の設定

スワップチェイン作成に必要な設定をします。

下のような感じです。

// SampleApp::Init()
    // DXGI_SWAP_CHAIN_DESC の設定
    DXGI_SWAP_CHAIN_DESC swapChainDesc;
    ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));

    swapChainDesc.BufferDesc.Width = static_cast<UINT>(clientSize.width);
    swapChainDesc.BufferDesc.Height = static_cast<UINT>(clientSize.height);
    swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
    swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
    swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_CENTERED;
    swapChainDesc.BufferDesc.Format = m_BufferFormat;
    swapChainDesc.SampleDesc = sampleDesc;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
    swapChainDesc.BufferCount = m_BufferCount;
    swapChainDesc.Windowed = TRUE;
    swapChainDesc.OutputWindow = hWnd;
    swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

 

DXGI_SWAP_CHAIN_DESC 構造体

typedef struct DXGI_SWAP_CHAIN_DESC {
DXGI_MODE_DESC BufferDesc;
DXGI_SAMPLE_DESC SampleDesc;
DXGI_USAGE BufferUsage;
UINT BufferCount;
HWND OutputWindow;
BOOL Windowed;
DXGI_SWAP_EFFECT SwapEffect;
UINT Flags;
} DXGI_SWAP_CHAIN_DESC;

BufferDesc.Width
 バッファの横幅です。

BufferDesc.Height
 バッファの縦幅です。

BufferDesc.RefreshRate.Numerator
 リフレッシュレートの分子です。

BufferDesc.RefreshRate.Denominator
 リフレッシュレートの分母です。

BufferDesc.ScanlineOrdering
 スキャンラインの方法です。

 バックバッファをフリップした時にハードウェアがパソコンのモニターに点をどう描くかを指定します。

 プログレッシブやインターレスなどが選択可能ですが、
 特段理由が無ければデフォルト値(DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED)でOKです。

BufferDesc.Scaling
 バッファのスケーリングの設定です。

 ウィンドウのサイズに応じてスケーリングするかどうかの設定を行えます。

 スケーリングする場合は DXGI_MODE_SCALING_STRETCHED 、
 スケーリングしない場合は DXGI_MODE_SCALING_CENTERED を指定します。

BufferDesc.Format
 バッファのフォーマットです。

 フォーマットの設定についてはかなりの種類があるので MSDN を参考にしてください。

SampleDesc
 MSAA の設定です。
 
 「4.MSAA (Multi-Sample Anti-Aliasing) の設定」で取得した値を設定します。

 何も指定しなかった場合は MSAA が無効になります。

BufferUsage
 バッファの使用方法です。

 レンダーターゲットとして使用する場合は DXGI_USAGE_RENDER_TARGET_OUTPUT 、
 シェーダー入力用(レンダリングテクスチャ)として使用する場合は DXGI_USAGE_SHADER_INPUT を指定します。

BufferCount
 バッファの数です。

 2ならダブルバッファ、3ならトリプルバッファとなります。

 特別こだわりがなければ2で問題ないと思います。

Windowed
 ウィンドウモードで作成するかどうかです。

 TRUE ならウィンドウモード、FALSE ならフルスクリーンモードです。

OutputWindow
 出力ウィンドウです。ウィンドウハンドルを指定します。

Flags
 スワップチェインの動作オプションです。

 DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH を指定した場合、
 フルスクリーンモードとウィンドウモードの切り替えが可能になります。

6.IDXGISwapChain の作成

やっとスワップチェインの作成です。

IDXGIFactory::CreateSwapChain 関数で作成します。

// SampleApp::Init()
    // スワップチェインの生成
    hr = m_Factory->CreateSwapChain(
            m_Device.Get(),
            &swapChainDesc,
            &m_SwapChain
        );
    if (FAILED(hr))
    {
        return false;
    }

 

IDXGIFactory::CreateSwapChain 関数

HRESULT CreateSwapChain(
IUnknown *pDevice,
DXGI_SWAP_CHAIN_DESC *pDesc,
IDXGISwapChain **ppSwapChain
);

pDevice
 スワップ チェーンに 2D イメージを書き込むデバイスへのポインター
 D3D11CreateDevice で作成したデバイスを指定します。

pDesc
 DXGI_SWAP_CHAIN_DESC へのポインターです。
 NULL は指定できません。

ppSwapChain
 作成されたスワップ チェーンへのポインターです。



ここまで記事を書いてきたのですが、
長くなったので続きは次回にしようと思います。

次回はレンダーターゲットの作成と画面クリアの処理についてです。

テストアプリケーション用テンプレートを作った

テスト用アプリケーションを作成しやすいように、テンプレート的なものを作りました。

アプリケーションクラスの作成

ウィンドウ作成などのWin32APIの面倒な処理をこのクラスが担当します。
一応、他プラットフォームでも対応できるようにインターフェイスクラスとして宣言しています。

KEY_CODE は列挙型で、別のヘッダーファイルで宣言されています。

AppMain関数がメインエントリポイントになります。

App.hpp

#pragma once

struct Position2D
{
    s32 x;
    s32 y;
};

struct Size2D
{
    u32 width;
    u32 height;
};

// コールバック登録用
struct AppCallbacks
{
    // リサイズ
    void(*pOnResize)(const Size2D& newSize, void* pUser);
    void* pOnResizeUser;

    // キーダウン
    void(*pOnKeyDown)(KEY_CODE key, void* pUser);
    void* pOnKeyDownUser;

    // キーアップ
    void(*pOnKeyUp)(KEY_CODE key, void* pUser);
    void* pOnKeyUpUser;

    // マウスキーダウン
    void(*pOnMouseKeyDown)(const Position2D& position, MOUSE_BUTTON button, void* pUser);
    void* pOnMouseKeyDownUser;

    // マウスキーアップ
    void(*pOnMouseKeyUp)(const Position2D& position, MOUSE_BUTTON button, void* pUser);
    void* pOnMouseKeyUpUser;

    // マウスホイール
    void(*pOnMouseWheel)(const Position2D& position, s32 delta, void* pUser);
    void* pOnMouseWheelUser;
};

// アプリケーションインターフェイス
class IApp
{
public:
    virtual ~IApp() = default;

    // コールバックを登録
    virtual void SetAppCallbacks(const AppCallbacks& callbacks) = 0;

    // true の間、メインループを実行
    virtual bool IsLoop() const = 0;

    // アプリケーションを終了
    virtual void PostQuit() = 0;

    // クライアント領域のサイズを取得
    virtual Size2D GetClientSize() const = 0;

    // ウィンドウハンドルを取得
    virtual void* GetWindowHandle() const = 0;

    // メッセージボックスを表示
    virtual void ShowMessageBox(const std::string& message, const std::string& caption) = 0;
};

// IApp ファクトリーメソッド
bool CreateApp(const Size2D& clientSize, const std::string& title, IApp ** ppOut);

// メインエントリポイント
void AppMain();

実装例

AppMain.cppではAppMain関数の定義と各種コールバックの登録を行っています。
AppMain関数内でコールバックの登録後、IApp::IsLoop()がtrueの間メインループを実行します。

AppMain.cpp

#define UNUSED(x) (x)

// リサイズ
void OnResize(const Size2D& newSize, void* pUser)
{
    UNUSED(newSize);
    UNUSED(pUser);
}

// キーダウン
void OnKeyDown(KEY_CODE key, void* pUser)
{
    IApp* pApp = reinterpret_cast<IApp*>(pUser);
    if (pApp == nullptr)
    {
        return;
    }

    if (key == KEY_CODE_ESCAPE)
    { // Escapeキーで終了
        pApp->PostQuit();
    }
}

// キーアップ
void OnKeyUp(KEY_CODE key, void* pUser)
{
    UNUSED(key);
    UNUSED(pUser);
}

// マウスキーダウン
void OnMouseKeyDown(const Position2D& position, MOUSE_BUTTON button, void* pUser)
{
    UNUSED(position);
    UNUSED(button);
    UNUSED(pUser);
}

// マウスキーアップ
void OnMouseKeyUp(const Position2D& position, MOUSE_BUTTON button, void* pUser)
{
    UNUSED(position);
    UNUSED(button);
    UNUSED(pUser);
}

// マウスホイール
void OnMouseWheel(const Position2D& position, s32 delta, void* pUser)
{
    UNUSED(position);
    UNUSED(delta);
    UNUSED(pUser);
}

// シーン更新処理
void UpdateScene()
{

}

// シーン描画処理
void RenderScene()
{

}

// エントリポイント
void AppMain()
{
    IApp* pApp = nullptr;
    Size2D clientSize = { 640, 480 };

    if (!CreateApp(clientSize, "Sample", &pApp))
    {
        return;
    }

    // コールバックの設定
    AppCallbacks callbacks = { 0 };
    // リサイズ
    callbacks.pOnResize = OnResize;
    callbacks.pOnResizeUser = pApp;
    // キーダウン
    callbacks.pOnKeyDown = OnKeyDown;
    callbacks.pOnKeyDownUser = pApp;
    // キーアップ
    callbacks.pOnKeyUp = OnKeyUp;
    callbacks.pOnKeyUpUser = pApp;
    // マウスキーダウン
    callbacks.pOnMouseKeyDown = OnMouseKeyDown;
    callbacks.pOnMouseKeyDownUser = pApp;
    // マウスキーアップ
    callbacks.pOnMouseKeyUp = OnMouseKeyUp;
    callbacks.pOnMouseKeyUpUser = pApp;
    // マウスホイール
    callbacks.pOnMouseWheel = OnMouseWheel;
    callbacks.pOnMouseWheelUser = pApp;

    pApp->SetAppCallbacks(callbacks);

    // メインループ
    while (pApp->IsLoop())
    {
        UpdateScene();
        RenderScene();
    }
    delete pApp;
}

できるだけ使いやすく作ったつもりですが、もう少しシンプルにできたかもしれません。
コードはGitHubにて公開していますので、ご自由にお使いください。

github.com

Visual Studioのビルドログファイルを出力されないようにする

通常、Visual Studioでビルドすると「.tlog」というフォルダが自動で作成されるのですが、

できるだけビルド環境はきれいにしておきたいので出力されないように設定しました。

 

メニューバーの

ツール(T) > オプション(O)...

でオプションウィンドウを開きます。

 

開いたら、検索窓に「VC++ プロジェクトの設定」と入力し、

出てきた設定を選びます。

 

その設定の一番上の「ビルドのログ」を「いいえ」変更し、「OK」ボタンを押すと完了です。

 

f:id:n-suudai:20180421210022p:plain

プリプロセッサシンボルの難解なメモリー破壊バグ

初めて書く記事がメモリー破壊バグについてとは…。

 

STL( Standard Template Library) では

プリプロセッサシンボル「_DEBUG」の定義ありの場合となしの場合で

確保するメモリーサイズが違うことにより、

モリー破壊が起こるという難解なバグが発生。

 

複数プロジェクトで構成されるアプリケーションで、

各々のプロジェクトでプリプロセッサシンボルを定義している場合は要注意です!

 

対策として、

大事なプリプロセッサシンボルは別のプロパティシートで定義しておき、

プロジェクトから参照するようにするのが望ましいのではないかと思います。

 

このブログについて

ゲーム開発において、学んだことや詰まったことなどを

情報共有できればと思っています。

 

最近はゲームエンジンが盛り上がっていますが、

私の会社では使う機会が少ないです。。。