Tutorial Sample Application

This page will focus on explaining the tutorial application and how the Genvid API is integrated into it.

  1. Origin
  2. Content
  3. Classes
  4. Genvid integration
  5. Other files

For details on how to run the tutorial, please refer to Local Environment Usage.

Origin

The tutorial is based off an official D3D11 sample from Microsoft: https://msdn.microsoft.com/en-us/library/windows/apps/ff729724.aspx We modified the sample to present properly the features available with the Genvid integration.

Content

The tutorial application displays 3 cubes named Athos, Porthos and Aramis. These cubes are displayed rotating and moving in various fixed directions. Upon reaching the extremity of the world, a cube is moved to the opposite side of the world to continue its movement without interruption. The player is in control of one cube at a time. Here’s a list of the controls available to the player:

  • The player is able to use the arrows and WASD keys to modify the trajectory of the selected cube. Modifying the direction also directly affects the rotation of the object.
  • The player is able to change the selection cube by pressing 1, 2 or 3.
  • The player can change the selected cube’s color by pressing a number of the numeric keypad.
  • The player can reset the orientation of the selected cube by pressing the O key.
  • The player can reset the position of the selected cube by pressing the P key.
  • The player can stop the cube from moving by pressing the Backspace key.

Classes

  • Tutorial.cpp : This file contains the window initialization process, the DirectX rendering, the game interaction controls and all of the Genvid SDK integration.
  • Cube.h : Header file for the cubes that are displayed in the game.
  • Cube.cpp : Class that contains all the transformation functions for the cubes.
  • DDSTextureLoader.h : Header file for loading a DDS texture and creating a Direct3D 11 runtime resource for it.
  • DDSTextureLoader.cpp : Class that contains all the functions for loading a DDS texture and creating a Direct3D 11 runtime resource for it.
  • JsonConversion.h : Header file for converting various data into JSON format to send via the stream afterwards.
  • JsonConversion.cpp : Class that convert various data type into JSON format to send via the stream afterwards.

Genvid integration

The Genvid SDK was integrated at various locations in the Tutorial application. We will display at which location they were integrated with code sample and line number to help you navigate through the tutorial application.

We start by including the Genvid Native SDK header file, and declare a few static variables:

#include <genvid.h>

static const std::string sStream_Audio = "Audio";
static const std::string sStream_Video = "Video";
static const std::string sStream_GameData = "GameData";
static const std::string sStream_Popularity = "Popularity";
static const std::string sStream_ColorChanged = "ColorChanged";

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

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

static std::string oldPopularityJSON;

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

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

system_clock::time_point gStartTime = system_clock::now();
double gWorldTime = 0.0;

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

XMVECTORF32 gBackgroundColor = Colors::DodgerBlue;

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]},
};

After setting up DirectX in InitDevice(), we call InitGenvid() which takes care of initializing the Genvid library, defines all of the streams that we will need, and configures all event and command subscriptions:

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;

    // Specify auto-capture video source.
    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;

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

    // Create stream for game data.
    gs = Genvid_CreateStream(sStream_GameData.c_str());
    if (GENVID_FAILED(gs))
        return E_FAIL;

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

    gs = Genvid_CreateStream(sStream_ColorChanged.c_str());
    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;
}

We currently have only one video stream and no audio stream. We send the video IDXGISwapChain via Genvid_SetParameterPointer(). We create a stream to send the game data to the servers. Afterwards, we create all the subscribes we need to catch the events coming from the webpage. In our case, we are catching 3 actions that can occur from the webpage: a change of color, a position reset and a selection change. The function GenvidSubscriptionCallback() is used to manage the event catching portion which will be displayed later on. Considering we are sending 3 types of events from the webpage, we need to call Genvid_Subscribe() 3 times with the proper name (these name must exactly the same used on the webpage and map reduce rules).

Right before presenting the back buffer, we auto-capture the video frame (using the previsouly specified IDXGISwapChain) and send it to Genvid. We also gather game data (cube position, orientation, and camera matrix), format it into JSON format for the website, and submit it with the same timecode as the video data. This is done in the utility routine GetGameDataJSON(). We then call Genvid_CheckForEvents() to see if we have received any event from the website.

    GenvidTimecode tc = Genvid_GetCurrentTimecode();

    Genvid_SubmitVideoData(tc, sStream_Video.c_str(), nullptr, 0);

    std::string gameData = GetGameDataJSON();
    Genvid_SubmitGameData(tc, sStream_GameData.c_str(), gameData.data(), (int)gameData.size());

    // Send the popularity as a notification
    std::string popularityJSON = GetPopularityJSON();
    if (popularityJSON != oldPopularityJSON)
    {
        Genvid_SubmitNotification(sStream_Popularity.c_str(), popularityJSON.data(), (int)popularityJSON.size());
    }
    oldPopularityJSON = popularityJSON;

    Genvid_CheckForEvents();

Calling Genvid_CheckForEvent() might invoke the function GenvidSubscriptionCallback(). This function is used to catch the events sent from the webpage to the game. In this case, we are evaluating the summary ID value to know which event was sent. Afterwards, we interpret the results key fields as parameters to the event.

//--------------------------------------------------------------------------------------
// Callback invoked when Genvid Events are received.
//--------------------------------------------------------------------------------------
void GenvidSubscriptionCallback(const GenvidEventSummary* summary, void* /*userData*/)
{
    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];
        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);
                }
            }
        }
        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)
            {
                g_cubeList[i].Cheer(float(summary->results[0].values[0].value));
                return;
            }
        }
    }
}

The structure of the summary is consistent with the map-reduce definition that we provided alongside the application (in mr.json):

{
  "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
    }
  ]
}

Calling Genvid_CheckForEvent() might also invoke the function GenvidSubscriptionCommandCallback(). This is where commands are interpreted.

void GenvidSubscriptionCommandCallback(const GenvidCommandResult* result, void* /*userData*/)
{
    std::string id(result->id);
    std::string value(result->value);
    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);
                return;
            }
        }
    }
    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);
                return;
            }
        }
    }
    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);
                return;
            }
        }
    }
}

The command’s value is a simple string, and it is game responsibility to parse it and interpret it correctly.

In the TermGenvid() function, we perform every action necessary for a proper cleanup of the Genvid SDK:

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_ColorChanged.c_str());
    Genvid_DestroyStream(sStream_Popularity.c_str());
    Genvid_DestroyStream(sStream_GameData.c_str());
    Genvid_DestroyStream(sStream_Video.c_str());
    Genvid_DestroyStream(sStream_Audio.c_str());

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

We need to unsubscribe every stream and commands we originally subscribed to. Afterwards, we destroy 2 streams (one for the video and one for the game data) and finish with a Genvid_Terminate().

Other files

  • demo.fx : File used for the shaders.
  • demo.rc : File used for the resources.
  • demo_ps.hlsl : File used for the pixel shaders.
  • demo_vs.hlsl: File used for the vertex shaders.
  • directx.ico: File used for the icon of the executable.
  • mr.json: File containing the map reduce for the events.
  • Tutorial.sln : Visual Studio project file.
  • Tutorial.vcxproj: File containing the settings of the tutorial.
  • Tutorial.vcxproj.filters: File containing the settings of the tutorial.