DirectX サンプル用アプリケーション

Genvid MILE SDK を使用するには、アプリケーションの様々なセクションに統合する必要があります。ここでは、DirectX サンプルを使って、SDK の統合方法を紹介します。コードのサンプルや行番号などを参考に、DirectX アプリケーションを操作してください。

注釈

DirectX Cube サンプルを ローカル環境 上で設定して実行すると、このセクションの統合手順をより理解しやすくなります。詳細は DirextX Cube サンプルの実行方法 を参照してください。

Classes

directx.cpp このファイルには、ウィンドウ初期化プロセス、DirectX レンダリング、ゲーム双方向操作、Genvid MILE SDK 統合に関するすべてが含まれています。
Cube.h ゲームで表示するキューブのヘッダーファイル。
Cube.cpp キューブの変形機能をすべて含む class。
DDSTextureLoader.h DDS テクスチャの読み込みと、Direct3D 11 ランタイムリソースの作成用ヘッダーファイル。
DDSTextureLoader.cpp DDS テクスチャの読み込みと、Direct3D 11 ランタイムリソースの作成用ヘッダーファイル。
JsonConversion.h さまざまなデータを JSON 形式に変換し、ストリームで送信するためのヘッダーファイル。
JsonConversion.cpp さまざまな形式のデータを JSON 形式に変換し、ストリームで送信するための class。

ヘッダーと宣言

初めに、Genvid Native SDK ヘッダーファイルをインクルードし、静的変数を宣言します。

#include <genvid.h>

// AV streams
static const std::string sStream_Audio = "Audio";
static const std::string sStream_Video = "Video";

// game data streams
static const std::string sStream_Names   = "Names";
static const std::string sStream_Colors = "Colors";
static const std::string sStream_Position  = "Positions";
static const std::string sStream_Camera = "Camera";
static const std::string sStream_Copyright = "Copyright";

// events
static const std::string sEvent_changeColor = "changeColor";
static const std::string sEvent_reset       = "reset";
static const std::string sEvent_cheer       = "cheer";

// commands
static const std::string sCommand_speed     = "speed";
static const std::string sCommand_direction = "direction";
static const std::string sCommand_reset     = "reset";

static void GenvidSubscriptionCallback(const GenvidEventSummary * summary, void * userData);

static void GenvidSubscriptionCommandCallback(const GenvidCommandResult * summary, void * userData);

int64_t gCurrentTime    = 0;
int64_t gPrevTime       = 0;
int64_t gFirstFrameTime = 0;

system_clock::time_point gStartTime        = system_clock::now();
double                   gWorldTime        = 0.0;
float                    gGenvidFramerate  = 30.0f; // uses default value
float                    gGameFramerate = 30.0f; // uses default value
uint32_t                 gGamePresentRatio = 0;     // uses default value
bool                     gEnableVSync     = true;

const float gCameraFOV    = XM_PIDIV4;
float          gCameraAspect   = 16.0f / 9.0f;
const float gCameraNear  = 0.01f;
const float gCameraFar    = 100.0f;
const XMVECTOR gCameraPosition = XMVectorSet(0.0f, 3.0f, -6.0f, 0.0f);
const XMVECTOR gCameraLookAt   = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
const XMVECTOR gCameraUp       = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

bool gSilent = false; // Global flag to kill all sounds.

XMVECTORF32 gBackgroundColor        = Colors::Blue;
XMVECTORF32 gBackgroundColorArray[] = {Colors::Blue, Colors::Green, Colors::Red, Colors::Orange, Colors::Bisque, Colors::CadetBlue, Colors::Indigo, Colors::Yellow};

XMFLOAT4 gColors[] = {
    {0.6f, 0.1f, 0.4f, 1.0f},
    {0.2f, 0.9f, 0.1f, 1.0f},
    {0.7f, 0.7f, 0.7f, 1.0f},
    {0.9f, 0.9f, 0.1f, 1.0f},
    {0.1f, 0.2f, 0.3f, 1.0f},
    {0.3f, 0.3f, 0.3f, 1.0f},
    {0.2f, 0.4f, 0.6f, 1.0f},
    {0.5f, 0.3f, 0.2f, 1.0f},
    {0.1f, 0.5f, 0.9f, 1.0f},
    {0.4f, 0.2f, 0.4f, 1.0f},
};

std::map<std::string, XMFLOAT4> sNameToColor = {
    {"rose", gColors[0]},
    {"green", gColors[1]},
    {"white", gColors[2]},
    {"yellow", gColors[3]},
    {"dark blue", gColors[4]},
    {"gray", gColors[5]},
    {"light blue", gColors[6]},
    {"orange", gColors[7]},
    {"blue", gColors[8]},
    {"purple", gColors[9]},
};

初期化

InitDevice() に DirectX をセットアップ後、InitGenvid() を呼び出すと、次のような動作をします。

  • Genvid ライブラリを初期化します。
  • 必要なすべてのストリームを定義します。
  • すべてのイベントとコマンドサブスクリプションを設定します。

このサンプルには、ビデオストリームとオーディオストリームが 1 つずつしかありません。

Genvid_SetParameterPointer() で、ビデオ IDXGISwapChain を送信します。

ゲームデータをサーバーに送信するストリームを作成していきます。1 つは 数 (popularity) の変更用、もう 1 つは色の変更用です。

最後に、Web ページからのイベントやコマンドをキャッチするために必要なすべてのサブスクリプションを作成します。

ここでは、Web ページで発生する 3 つのアクションを受け取ります。

  • 色の変更
  • 位置のリセット
  • 数 (popularity) の変更

また、Web ページで発生する 3 つのアクションをコマンドで受け取ります。

  • スピードの変更
  • 方向の変更
  • 位置のリセット

関数 GenvidSubscriptionCallback() は、後に表示するイベント受付部を制御します。ページから 3 種類のイベントを送信するため、Genvid_Subscribe() を適切な名称で 3 回呼び出す必要があります。

注釈

Genvid_Subscribe() の呼び出しに使用する名前は、Web ページや map-reduce で使用しているものと一致している必要があります。

HRESULT InitGenvid()
{
    GenvidStatus gs;

    // Initialize the Genvid Native SDK.
    gs = Genvid_Initialize();
    if (GENVID_FAILED(gs))
        return E_FAIL;

    // Create the audio stream.
    gs = Genvid_CreateStream(sStream_Audio.c_str());
    if (GENVID_FAILED(gs))
        return E_FAIL;

    // Set the frame.
    gs = Genvid_SetParameterFloat(sStream_Audio.c_str(), "framerate", gGameFramerate);
    if (GENVID_FAILED(gs))
        return E_FAIL;

    // Specify auto-capture video source.
    // Note: enabling WASAPI sets the audio granularity to the sampling rate.
    gs = Genvid_SetParameterInt(sStream_Audio.c_str(), "Audio.Source.WASAPI", 1);
    if (GENVID_FAILED(gs))
        return E_FAIL;

    // Create the video stream.
    gs = Genvid_CreateStream(sStream_Video.c_str());
    if (GENVID_FAILED(gs))
        return E_FAIL;

    // Set the frame.
    gs = Genvid_SetParameterFloat(sStream_Video.c_str(), "framerate", gGameFramerate);
    if (GENVID_FAILED(gs))
        return E_FAIL;

    if (gGenvidFramerate > 0)
    {
        gs = Genvid_SetParameterFloat(sStream_Video.c_str(), "framerate", gGenvidFramerate);
        if (GENVID_FAILED(gs))
            return E_FAIL;
    }

    gs = Genvid_CreateStream(sStream_Copyright.c_str());
    if (GENVID_FAILED(gs))
        return E_FAIL;

    gs = Genvid_CreateStream(sStream_Names.c_str());
    if (GENVID_FAILED(gs))
        return E_FAIL;

    gs = Genvid_CreateStream(sStream_Colors.c_str());
    if (GENVID_FAILED(gs))
        return E_FAIL;

    gs = Genvid_CreateStream(sStream_Position.c_str());
    if (GENVID_FAILED(gs))
        return E_FAIL;

    gs = Genvid_CreateStream(sStream_Camera.c_str());
    if (GENVID_FAILED(gs))
        return E_FAIL;

#if GENVID_USE_DXGISWAPCHAIN
    // Specify auto-capture video source.
    gs = Genvid_SetParameterPointer(sStream_Video.c_str(), "Video.Source.IDXGISwapChain", g_pSwapChain);
    if (GENVID_FAILED(gs))
        return E_FAIL;
#endif

    // these frames are received one time only by each viewer when the Genvid client is connected
    // we can send data that never changes once, just after the stream is created.
    std::string copyright = "Copyright 2021-2022 Genvid Technologies LLC";
    gs                    = Genvid_SubmitGameData(-1, sStream_Copyright.c_str(), copyright.c_str(), (int)copyright.size());

    if (GENVID_FAILED(gs))
        return E_FAIL;

    auto cubeNames = GetCubeNames();
    gs             = Genvid_SubmitGameData(-1, sStream_Names.c_str(), cubeNames.c_str(), (int)cubeNames.size());
    if (GENVID_FAILED(gs))
        return E_FAIL;

    char buffer[512];
    auto size = GetCameraData(buffer);
    gs        = Genvid_SubmitGameData(-1, sStream_Camera.c_str(), buffer, size);
    if (GENVID_FAILED(gs))
        return E_FAIL;

    // Subscribe to events.
    gs = Genvid_Subscribe(sEvent_changeColor.c_str(), &GenvidSubscriptionCallback, nullptr);
    if (GENVID_FAILED(gs))
        return E_FAIL;
    gs = Genvid_Subscribe(sEvent_reset.c_str(), &GenvidSubscriptionCallback, nullptr);
    if (GENVID_FAILED(gs))
        return E_FAIL;
    gs = Genvid_Subscribe(sEvent_cheer.c_str(), &GenvidSubscriptionCallback, nullptr);
    if (GENVID_FAILED(gs))
        return E_FAIL;

    // Subscribe to commands.
    gs = Genvid_SubscribeCommand(sCommand_speed.c_str(), &GenvidSubscriptionCommandCallback, nullptr);
    if (GENVID_FAILED(gs))
        return E_FAIL;
    gs = Genvid_SubscribeCommand(sCommand_direction.c_str(), &GenvidSubscriptionCommandCallback, nullptr);
    if (GENVID_FAILED(gs))
        return E_FAIL;
    gs = Genvid_SubscribeCommand(sCommand_reset.c_str(), &GenvidSubscriptionCommandCallback, nullptr);
    if (GENVID_FAILED(gs))
        return E_FAIL;

    return S_OK;
}

作成したイベントやコマンドを Web サイトに実装することを忘れないでください。参考例は イベント生成sendCommands のページにあります。Web サンプルの Web サンプル にある web.jsweb-admin.js ファイルでも、詳細に説明しています。

ストリーミング

バックバッファを表示する直前に、(あらかじめ指定した IDXGISwapChain を使用して) ビデオフレームを自動キャプチャして Genvid に送信します。

次に、以下のゲームデータを収集します。

  • キューブの位置。
  • 向き。
  • カメラマトリクス。

ゲームデータを Web サイトで使用する JSON 形式に変換します。

ユーティリティルーチン GetGameDataJSON() を使用してビデオデータと同じタイムコードで、ゲームデータを送信します。

次に 数データを収集し、変化の有無を検証します。変更がある場合、 更新した 数データを Genvid_SubmitNotification() で JSON に変換します。

最後に Genvid_CheckForEvents() を呼び出して、Web サイトからイベントを受信したかどうか確認します。

GenvidStatus   gs = GenvidStatus_Success;
GenvidTimecode tc = Genvid_GetCurrentTimecode();

auto positionData = GetCubePositions();
gs                = Genvid_SubmitGameData(tc, sStream_Position.c_str(), positionData.data(), (int)positionData.size());
assert(!GENVID_FAILED(gs));

// Submit popularity every second
if (gTimeSincePopularitySent_s >= popularitySendInterval_s)
{
    gTimeSincePopularitySent_s = 0.0f;
    auto popularityData        = GetCubePopularity();
    gs                         = Genvid_SubmitNotification("POPULARITY", popularityData.c_str(), (int)popularityData.size());
    assert(!GENVID_FAILED(gs));
}

auto colorData = GetCubeColors();
gs             = Genvid_SubmitGameData(tc, sStream_Colors.c_str(), colorData.c_str(), (int)colorData.size());
assert(!GENVID_FAILED(gs));

gs = Genvid_CheckForEvents();
assert(!GENVID_FAILED(gs));

GenvidSubscriptionCallback

Genvid_CheckForEvents() を呼び出すことで、GenvidSubscriptionCallback() 関数が実行される場合があります。この関数は、Web ページからゲームに送信されるイベントを受信します。呼び出す際には、

  1. summary ID 値を検証し、どのイベントが送信されたかを確認し、
  2. 生成したキーフィールドをパラメータとしてイベントに変換します。
//--------------------------------------------------------------------------------------
// Callback invoked when Genvid Events are received.
//--------------------------------------------------------------------------------------
void GenvidSubscriptionCallback(const GenvidEventSummary * summary, void * /*userData*/)
{
    std::cout << "color change received" << std::endl;
    if (summary->id == sEvent_changeColor)
    {
        // Change the color of the cube.
        const char * cubeName  = summary->results[0].key.fields[0];
        const char * cubeColor = summary->results[0].key.fields[1];
        // Finding the cube and change the color
        std::cout << "color change received " << cubeName << std::endl;
        for (int i = 0; i < g_cubeTotal; ++i)
        {
            if (g_cubeList[i].GetName() == cubeName)
            {
                std::map<std::string, XMFLOAT4>::iterator colorToFind = sNameToColor.find(cubeColor);
                if (colorToFind != sNameToColor.end())
                {
                    g_cubeList[i].SetColor(colorToFind->second);
                    std::cout << "color changed for " << cubeName << std::endl;
                }
            }
        }
        OnUpdateColor();
    }
    else if (summary->id == sEvent_reset)
    {
        // Reset the cube position.
        const char * cubeName = summary->results[0].key.fields[0];
        for (int i = 0; i < g_cubeTotal; ++i)
        {
            if (g_cubeList[i].GetName() == cubeName)
            {
                g_cubeList[i].ResetPosition();
                return;
            }
        }
    }
    else if (summary->id == sEvent_cheer)
    {
        // Handle cube cheering.
        const char * cubeName = summary->results[0].key.fields[0];
        for (int i = 0; i < g_cubeTotal; ++i)
        {
            if (g_cubeList[i].GetName() == cubeName)
            {
                // GENVID - Get cheer min value.
                g_cubeList[i].Cheer(float(summary->results[0].values[0].value));
                // GENVID - Cheer value set.
                return;
            }
        }
    }
}

サマリーの構造は、config/events.json でアプリケーションと一緒に提供される map-reduce の定義と一貫しています。

{
  "version": "1.7.0",
  "event": {
    "game": {
      "maps": [
        {
          "id": "changeColor",
          "source": "userinput",
          "where": {"key": ["changeColor", "<name>"], "name": "<color>", "type": "string"},
          "key": ["changeColor", "<name>", "<color>"], "value": 1
        },
        {
          "id": "reset",
          "source": "userinput",
          "where": {"key": ["reset"], "name": "<name>", "type": "string"},
          "key": ["reset", "<name>"], "value": 1
        },
        {
          "id": "cheer",
          "source": "userinput",
          "where": {"key": ["cheer"], "name": "<name>", "type": "string"},
          "key": ["cheer", "<name>"], "value": 1
        }
      ],
      "reductions": [
        {
          "id": "changeColor",
          "where": {"key": ["changeColor", "<name>", "<color>"]},
          "key": ["<name>", "<color>"],
          "value": ["$count"],
          "period": 250
        },
        {
          "id": "reset",
          "where": {"key": ["reset", "<name>"]},
          "key": ["<name>"],
          "value": ["$count"],
          "period": 250
        },
        {
          "id": "cheer",
          "where": {"key": ["cheer", "<name>"]},
          "key": ["<name>"],
          "value": ["$sum"],
          "period": 250
        }
      ]
    }
  }
}

GenvidSubscriptionCommandCallback

また、 Genvid_CheckForEvents() を呼び出すことで、 GenvidSubscriptionCommandCallback() 関数が実行される場合があります。これにより、コマンドの解釈が行われます。

コマンドの結果には、ID と値が含まれています。値を解析し、メソッドで使用する文字列に変換します。

void GenvidSubscriptionCommandCallback(const GenvidCommandResult * result, void * /*userData*/)
{
    std::string id(result->id);
    std::string value(result->value);
    std::cout << "Command: " << result->id << " - " << result->value << std::endl;
    if (id == "direction")
    {
        std::vector<std::string> elems;
        split(value, ':', elems);
        auto cubeName = elems[0];
        for (int i = 0; i < g_cubeTotal; ++i)
        {
            if (g_cubeList[i].GetName() == cubeName)
            {
                float x = (float)std::atof(elems[1].c_str());
                float y = (float)std::atof(elems[2].c_str());
                float z = (float)std::atof(elems[3].c_str());
                g_cubeList[i].ChangeDirection(x, y, z);
                break;
            }
        }
        std::string payload(cubeName + "'s direction was changed by admin");
        auto        gs = Genvid_SubmitNotification("DIRECTION", payload.c_str(), (int)payload.size());
        if (GENVID_FAILED(gs))
        {
            std::cout << "Fail to send notification." << std::endl;
        }
    }
    else if (id == "speed")
    {
        std::vector<std::string> elems;
        split(value, ':', elems);
        auto cubeName = elems[0];
        for (int i = 0; i < g_cubeTotal; ++i)
        {
            if (g_cubeList[i].GetName() == cubeName)
            {
                float speed = (float)std::atof(elems[1].c_str());
                auto  velocity = g_cubeList[i].GetLinearVelocity() * speed;
                g_cubeList[i].SetLinearVelocity(velocity);
                break;
            }
        }
        std::string payload(cubeName + "'s speed was changed by admin");
        auto        gs = Genvid_SubmitNotification("SPEED", payload.c_str(), (int)payload.size());
        if (GENVID_FAILED(gs))
        {
            std::cout << "Fail to send notification." << std::endl;
        }
    }
    else if (id == "reset")
    {
        auto cubeName = value;
        for (int i = 0; i < g_cubeTotal; ++i)
        {
            if (g_cubeList[i].GetName() == cubeName)
            {
                auto velocity = g_cubeList[i].GetInitialLinearVelocity();
                g_cubeList[i].ResetPosition();
                g_cubeList[i].ResetOrientation();
                g_cubeList[i].SetLinearVelocity(velocity);
                break;
            }
        }
        std::string payload(cubeName + "'s position was reset by admin");
        auto        gs = Genvid_SubmitNotification("RESET", payload.c_str(), (int)payload.size());
        if (GENVID_FAILED(gs))
        {
            std::cout << "Fail to send notification." << std::endl;
        }
    }
}

ライブ動画配信

サンプルでは、ライブ動画配信の 2 つのアプローチを紹介しています。自動キャプチャとマニュアルキャプチャです。自動キャプチャがデフォルトに設定されています。

キャプチャを行うには、null パラメータで Genvid_SubmitVideoData() 関数を呼び出します。

gs = Genvid_SubmitVideoData(tc, sStream_Video.c_str(), nullptr, 0);
assert(!GENVID_FAILED(gs));

状況によっては、マニュアルキャプチャを使用する必要がある場合があります。DirectX11 を使用していない場合や、レンダリングスレッドを使用しないでデータを Genvid に渡したい場合などです。

マニュアルキャプチャを使用するには、正しいパラメータを使ってビデオフレームを設定する必要があります。

assert(description.Format == DXGI_FORMAT_R8G8B8A8_UNORM);
gs = Genvid_SetParameterInt(sStream_Video.c_str(), "video.pixel_format", GenvidPixelFormat_R8G8B8A8);
assert(gs == GenvidStatus_Success);
gs = Genvid_SetParameterInt(sStream_Video.c_str(), "video.width", description.Width);
assert(gs == GenvidStatus_Success);
gs = Genvid_SetParameterInt(sStream_Video.c_str(), "video.height", description.Height);
assert(gs == GenvidStatus_Success);

バッファをキャプチャして、Genvid にサブミットします。

// Figure out what we need on the first run.
if (g_pVideoBuffer == nullptr)
{
    // Hardcoded for DXGI_FORMAT_R8G8B8A8_UNORM
    g_NumBytes   = description.Height * description.Width * 4;
    g_pVideoBuffer = new uint8_t[g_NumBytes];
}
// On the following runs, check just in case.
else
{
    // Hardcoded for DXGI_FORMAT_R8G8B8A8_UNORM
    uint32_t TempNumBytes = description.Height * description.Width * 4;
    if (TempNumBytes != g_NumBytes)
    {
        delete[] g_pVideoBuffer;

        g_pVideoBuffer = new uint8_t[TempNumBytes];
        g_NumBytes   = TempNumBytes;
    }
}

uint8_t * pDestination   = g_pVideoBuffer;
uint8_t * pSource        = (uint8_t *)resource.pData;
uint32_t  NumSourceBytes = resource.RowPitch;
// Hardcoded for DXGI_FORMAT_R8G8B8A8_UNORM
uint32_t NumDestinationBytes = description.Width * 4;
uint32_t NumSourceLines      = description.Height;

for (uint32_t i = 0; i < NumSourceLines; ++i)
{
    memcpy(pDestination, pSource, NumDestinationBytes);
    pSource += NumSourceBytes;
    pDestination += NumDestinationBytes;
}

gs = Genvid_SubmitVideoData(tc, sStream_Video.c_str(), g_pVideoBuffer, g_NumBytes);
assert(!GENVID_FAILED(gs));

g_pImmediateContext->Unmap(pNewTexture, subresource);

終了

TermGenvid() 関数で、Genvid MILE SDK を適切にクリーンアップするために必要なすべてのアクションを実行します。

ストリーム、イベント、コマンドのサブスクリプションをすべて解除する必要があります。その後 Genvid_Terminate() で、すべてのストリームを破棄して終了します。

void TermGenvid()
{
    // Cancel command subscriptions.
    Genvid_UnsubscribeCommand(sCommand_reset.c_str(), &GenvidSubscriptionCommandCallback, nullptr);
    Genvid_UnsubscribeCommand(sCommand_direction.c_str(), &GenvidSubscriptionCommandCallback, nullptr);
    Genvid_UnsubscribeCommand(sCommand_speed.c_str(), &GenvidSubscriptionCommandCallback, nullptr);

    // Cancel event subscriptions.
    Genvid_Unsubscribe(sEvent_cheer.c_str(), &GenvidSubscriptionCallback, nullptr);
    Genvid_Unsubscribe(sEvent_reset.c_str(), &GenvidSubscriptionCallback, nullptr);
    Genvid_Unsubscribe(sEvent_changeColor.c_str(), &GenvidSubscriptionCallback, nullptr);

    // Destroy the streams.
    Genvid_DestroyStream(sStream_Position.c_str());
    Genvid_DestroyStream(sStream_Copyright.c_str());
    Genvid_DestroyStream(sStream_Camera.c_str());
    Genvid_DestroyStream(sStream_Names.c_str());
    Genvid_DestroyStream(sStream_Colors.c_str());
    Genvid_DestroyStream(sStream_Video.c_str());
    Genvid_DestroyStream(sStream_Audio.c_str());

    // Terminate the Genvid Native SDK.
    Genvid_Terminate();
}

その他のファイル

demo.fx シェーダに使用するファイル。
demo.rc リソースに使用するファイル。
demo_ps.hlsl ピクセルシェーダに使用するファイル。
demo_vs.hlsl バーテックスシェーダに使用するファイル。
directx.ico デモの実行ファイルに使用するアイコン。
directx.sln Visual Studio のプロジェクトソリューションファイル。
directx.vcxproj Visual Studio のプロジェクト設定ファイル。
directx.vcxproj.filters Visual Studio のプロジェクトフィルタファイル。