/* Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. * * NVIDIA CORPORATION and its licensors retain all intellectual property * and proprietary rights in and to this software, related documentation * and any modifications thereto. Any use, reproduction, disclosure or * distribution of this software and related documentation without an express * license agreement from NVIDIA CORPORATION is strictly prohibited. */ /*! * \file * Shows example usage of the GfeSDK C++ API by a directX application. */ #include #include "GfeSDKWrapper.hpp" #include char g_logBuffer[512]; void dbgprint(const char *fmt, ...) { va_list args; va_start(args, fmt); vsprintf_s(g_logBuffer, sizeof(g_logBuffer) / sizeof(g_logBuffer[0]), fmt, args); printf(g_logBuffer); printf("\n"); OutputDebugStringA(g_logBuffer); OutputDebugStringA("\n"); va_end(args); } #define LOG(fmt, ...) dbgprint(fmt, __VA_ARGS__) static std::wstring permissionToStringW(GfeSDK::NVGSDK_Permission p) { switch (p) { case GfeSDK::NVGSDK_PERMISSION_MUST_ASK: return L"Must Ask"; case GfeSDK::NVGSDK_PERMISSION_GRANTED: return L"Granted"; case GfeSDK::NVGSDK_PERMISSION_DENIED: return L"Denied"; default: return L"UNKNOWN"; } } GfeSdkWrapper::GfeSdkWrapper() : m_currentPermission(permissionToStringW(GfeSDK::NVGSDK_PERMISSION_MUST_ASK)) { } void GfeSdkWrapper::Init(char const* gameName, char const* defaultLocale, GfeSDK::NVGSDK_Highlight* highlights, size_t numHighlights) { using namespace std::placeholders; //! [Creation CPP] GfeSDK::CreateInputParams createParams; createParams.appName = "gfesdk_dx_sample"; // appName will only be used/visible if GFE cannot identify your game createParams.pollForCallbacks = true; // We will poll for callbacks in order to execute callbacks from game loop createParams.requiredScopes = { GfeSDK::NVGSDK_SCOPE_HIGHLIGHTS, GfeSDK::NVGSDK_SCOPE_HIGHLIGHTS_VIDEO, GfeSDK::NVGSDK_SCOPE_HIGHLIGHTS_SCREENSHOT }; createParams.notificationCallback = std::bind(&GfeSdkWrapper::OnNotification, this, _1, _2); GfeSDK::CreateResponse response; GfeSDK::Core* gfesdkCore = GfeSDK::Core::Create(createParams, response); if (GfeSDK::NVGSDK_SUCCEEDED(response.returnCode)) { // Valid handle has been returned LOG("Success: %s", GfeSDK::NVGSDK_RetCodeToString(response.returnCode)); LOG("PC is running GFE version %s", response.nvidiaGfeVersion.c_str()); LOG("PC is running GfeSDK version %d.%d", response.versionMajor, response.versionMinor); switch (response.returnCode) { case GfeSDK::NVGSDK_SUCCESS_VERSION_OLD_GFE: LOG("Compatible, but the user should update to the latest version of GFE."); break; case GfeSDK::NVGSDK_SUCCESS_VERSION_OLD_SDK: LOG("Compatible, but this application should update to a more recent GfeSDK to get latest features/bugfixes"); break; } } else { // No valid handle LOG("Failure: %s", GfeSDK::NVGSDK_RetCodeToString(response.returnCode)); switch (response.returnCode) { case GfeSDK::NVGSDK_ERR_SDK_VERSION: LOG("This version of the SDK is too old to communicate with the user's SDK. We're never planning on this happening."); LOG("PC is running GFE version %s", response.nvidiaGfeVersion.c_str()); LOG("PC is running GfeSDK version %d.%d", response.versionMajor, response.versionMinor); break; case GfeSDK::NVGSDK_SUCCESS_VERSION_OLD_SDK: LOG("The installed version of GFE is too old to continue. User must upgrade."); LOG("PC is running GFE version %s", response.nvidiaGfeVersion.c_str()); LOG("PC is running GfeSDK version %d.%d", response.versionMajor, response.versionMinor); break; } return; } //! [Creation CPP] m_gfesdk.reset(gfesdkCore); UpdateLastResultString(response.returnCode); if (response.scopePermissions.find(GfeSDK::NVGSDK_SCOPE_HIGHLIGHTS_VIDEO) != response.scopePermissions.end()) { m_currentPermission = permissionToStringW(response.scopePermissions[GfeSDK::NVGSDK_SCOPE_HIGHLIGHTS_VIDEO]); } //! [Permissions CPP] // Request Permissions if user hasn't decided yet GfeSDK::RequestPermissionsParams requestPermissionsParams; // 'response' came from create call. It tells us which permissions we requested during Create, // but the user hasn't yet made a decision on for (auto&& entry : response.scopePermissions) { if (entry.second == GfeSDK::NVGSDK_PERMISSION_MUST_ASK) { requestPermissionsParams.scopes.push_back(entry.first); } } if (!requestPermissionsParams.scopes.empty()) { // If the user hasn't given permission for recording yet, ask them to do so now via overlay m_gfesdk->RequestPermissionsAsync(requestPermissionsParams, [this, defaultLocale, highlights, numHighlights](GfeSDK::NVGSDK_RetCode rc, void* cbContext) { UpdateLastResultString(rc); if (GfeSDK::NVGSDK_SUCCEEDED(rc)) { ConfigureHighlights(defaultLocale, highlights, numHighlights); } }); } else { // Otherwise, go ahead and set up now ConfigureHighlights(defaultLocale, highlights, numHighlights); } //! [Permissions CPP] } void GfeSdkWrapper::DeInit() { // Releasing from smart pointer just to document how to delete handle GfeSDK::Core* gfesdkCore = m_gfesdk.release(); //! [Release CPP] delete gfesdkCore; //! [Release CPP] } void GfeSdkWrapper::OnTick(void) { if (!m_gfesdk) return; // Poll for callbacks in order to execute these callbacks from the game update thread, // not a third party thead owned by GfeSDK. This lets us update game state from the callbacks // without causing a threading problem m_gfesdk->Poll(); } void GfeSdkWrapper::OnNotification(GfeSDK::NVGSDK_NotificationType type, GfeSDK::NotificationBase const& notification) { switch (type) { case GfeSDK::NVGSDK_NOTIFICATION_PERMISSIONS_CHANGED: { GfeSDK::PermissionsChangedNotification const& n = static_cast(notification); auto hlPermission = n.scopePermissions.find(GfeSDK::NVGSDK_SCOPE_HIGHLIGHTS_VIDEO); if (hlPermission != n.scopePermissions.end()) { m_currentPermission = permissionToStringW(hlPermission->second); } break; } case GfeSDK::NVGSDK_NOTIFICATION_OVERLAY_STATE_CHANGED: { GfeSDK::OverlayStateChangedNotification const& n = static_cast(notification); m_lastOverlayEvent.clear(); switch (n.state) { case GfeSDK::NVGSDK_OVERLAY_STATE_MAIN: m_lastOverlayEvent += L"Main Overlay Window"; break; case GfeSDK::NVGSDK_OVERLAY_STATE_PERMISSION: m_lastOverlayEvent += L"Permission Overlay Window"; break; case GfeSDK::NVGSDK_OVERLAY_STATE_HIGHLIGHTS_SUMMARY: m_lastOverlayEvent += L"Highlights Summary Overlay Window"; break; default: m_lastOverlayEvent += L"UNKNOWNWindow"; } m_lastOverlayEvent += (n.open ? L" OPEN" : L" CLOSE"); break; } } } void GfeSdkWrapper::OnOpenGroup(std::string const& id) { if (!m_highlights) return; //! [OpenGroup CPP] GfeSDK::HighlightOpenGroupParams params; params.groupId = id; params.groupDescriptionLocaleTable["en-US"] = id; m_highlights->OpenGroupAsync(params, [this](GfeSDK::NVGSDK_RetCode rc, void*) { UpdateLastResultString(rc); }); //! [OpenGroup CPP] } void GfeSdkWrapper::OnCloseGroup(std::string const& id, bool destroy) { if (!m_highlights) return; //! [CloseGroup CPP] GfeSDK::HighlightCloseGroupParams params; params.groupId = id; params.destroyHighlights = destroy; m_highlights->CloseGroupAsync(params, [this](GfeSDK::NVGSDK_RetCode rc, void*) { UpdateLastResultString(rc); }); //! [CloseGroup CPP] #ifdef DOXYGEN //! [Async Call No Callback] // If the caller doesn't care about the return value, no need to pass callbacks GfeSDK::HighlightCloseGroupParams params = { "GROUP_1" }; m_highlights->CloseGroupAsync(params); //! [Async Call No Callback] #endif } void GfeSdkWrapper::OnSaveScreenshot(std::string const& highlightId, std::string const& groupId) { GfeSDK::ScreenshotHighlightParams params; params.groupId = groupId; params.highlightId = highlightId; m_highlights->SetScreenshotHighlightAsync(params, [this](GfeSDK::NVGSDK_RetCode rc, void*) { UpdateLastResultString(rc); }); } void GfeSdkWrapper::OnSaveVideo(std::string const& highlightId, std::string const& groupId, int startDelta, int endDelta) { //! [SaveVideo CPP] GfeSDK::VideoHighlightParams params; params.startDelta = startDelta; params.endDelta = endDelta; params.groupId = groupId; params.highlightId = highlightId; m_highlights->SetVideoHighlightAsync(params, [this](GfeSDK::NVGSDK_RetCode rc, void*) { UpdateLastResultString(rc); }); //! [SaveVideo CPP] } void GfeSdkWrapper::OnGetNumHighlights(std::string const& groupId, GfeSDK::NVGSDK_HighlightSignificance sigFilter, GfeSDK::NVGSDK_HighlightType tagFilter) { GfeSDK::GroupView v; v.groupId = groupId; v.significanceFilter = sigFilter; v.tagsFilter = tagFilter; m_highlights->GetNumberOfHighlightsAsync(v, [this](GfeSDK::NVGSDK_RetCode rc, GfeSDK::GetNumberOfHighlightsResponse const* response, void*) { UpdateLastResultString(rc); if (GfeSDK::NVGSDK_SUCCEEDED(rc)) { m_lastQueryResult = std::to_wstring(response->numHighlights); } }); } void GfeSdkWrapper::OnOpenSummary(char const* groupIds[], size_t numGroups, GfeSDK::NVGSDK_HighlightSignificance sigFilter, GfeSDK::NVGSDK_HighlightType tagFilter) { //! [OpenSummary CPP] GfeSDK::SummaryParams params; // Can show more than one group at a time, each with their own filters if desired for (size_t i = 0; i < numGroups; ++i) { GfeSDK::GroupView v; v.groupId = groupIds[i]; v.significanceFilter = sigFilter; v.tagsFilter = tagFilter; params.groupViews.push_back(v); } m_highlights->OpenSummaryAsync(params, [this](GfeSDK::NVGSDK_RetCode rc, void*) { UpdateLastResultString(rc); }); //! [OpenSummary CPP] } void GfeSdkWrapper::OnRequestLanguage(void) { //! [Asynchonous Call] // We are passing two arguments to this function, the function lambda, and a user context. In this case, we're passing // the 'this' pointer as the user context. This gets passed through unmodified for use in the callback function. m_gfesdk->GetUILanguageAsync([this](GfeSDK::NVGSDK_RetCode rc, GfeSDK::GetUILanguageResponse const* response, void* context) { // Passed this pointer through as context GfeSdkWrapper* thiz = reinterpret_cast(context); UpdateLastResultString(rc); if (GfeSDK::NVGSDK_SUCCEEDED(rc)) { m_lastQueryResult = m_converter.from_bytes(response->cultureCode); } }, this); //! [Asynchonous Call] } void GfeSdkWrapper::OnRequestUserSettings(void) { m_highlights->GetUserSettingsAsync([this](GfeSDK::NVGSDK_RetCode rc, GfeSDK::GetUserSettingsResponse const* response, void*) { UpdateLastResultString(rc); m_lastQueryResult = L""; if (GfeSDK::NVGSDK_SUCCEEDED(rc)) { for (auto it = response->highlightSettings.begin(); it != response->highlightSettings.end(); ++it) { m_lastQueryResult += L"\n" + m_converter.from_bytes(it->highlightId) + (it->enabled ? L": ON" : L": OFF"); } } }); } wchar_t const* GfeSdkWrapper::GetCurrentPermissionStr(void) { return m_currentPermission.c_str(); } wchar_t const* GfeSdkWrapper::GetLastOverlayEvent(void) { return m_lastOverlayEvent.c_str(); } wchar_t const* GfeSdkWrapper::GetLastResult(void) { return m_lastResult.c_str(); } wchar_t const* GfeSdkWrapper::GetLastQueryResult(void) { return m_lastQueryResult.c_str(); } void GfeSdkWrapper::ConfigureHighlights(char const* defaultLocale, GfeSDK::NVGSDK_Highlight* highlights, size_t numHighlights) { //! [ConfigureHighlights CPP] // Create handle to highlights module m_highlights.reset(GfeSDK::Highlights::Create(m_gfesdk.get())); GfeSDK::HighlightConfigParams configParams; configParams.defaultLocale = defaultLocale; // Set up highlight definition table for (size_t i = 0; i < numHighlights; ++i) { GfeSDK::HighlightDefinition highlightDef; highlightDef.id = highlights[i].id; highlightDef.userDefaultInterest = highlights[i].userInterest; highlightDef.significance = highlights[i].significance; highlightDef.highlightTags = highlights[i].highlightTags; for (size_t j = 0; j < highlights[i].nameTableSize; ++j) { highlightDef.nameLocaleTable[highlights[i].nameTable[j].localeCode] = highlights[i].nameTable[j].localizedString; } configParams.highlightDefinitions.push_back(highlightDef); } m_highlights->ConfigureAsync(configParams, [this](GfeSDK::NVGSDK_RetCode rc, void*) { UpdateLastResultString(rc); }); //! [ConfigureHighlights CPP] } void GfeSdkWrapper::UpdateLastResultString(GfeSDK::NVGSDK_RetCode rc) { m_lastResult = m_converter.from_bytes(GfeSDK::RetCodeToString(rc)); }