diff --git a/External/Xal/External/libHttpClient b/External/Xal/External/libHttpClient index 6b822f62..b8e33583 160000 --- a/External/Xal/External/libHttpClient +++ b/External/Xal/External/libHttpClient @@ -1 +1 @@ -Subproject commit 6b822f62f3c3286ec2579b4885900bb38fa55591 +Subproject commit b8e335836e0c6bc6b83f943b1c9ad3c815c1aab0 diff --git a/External/Xal/Source/Xal/Include/Xal/xal_version.h b/External/Xal/Source/Xal/Include/Xal/xal_version.h index 0df50676..1fc4e2be 100644 --- a/External/Xal/Source/Xal/Include/Xal/xal_version.h +++ b/External/Xal/Source/Xal/Include/Xal/xal_version.h @@ -20,6 +20,6 @@ extern "C" /// YYYYMMDD Date string describing the date the build was created /// rrr QFE number (000 indicates base release) /// -#define XAL_VERSION "2022.06.20220603.000" +#define XAL_VERSION "2022.08.20220825.000" } diff --git a/Include/xsapi-c/achievements_c.h b/Include/xsapi-c/achievements_c.h index 2bcedb20..d0f76e72 100644 --- a/Include/xsapi-c/achievements_c.h +++ b/Include/xsapi-c/achievements_c.h @@ -165,7 +165,7 @@ enum class XblAchievementRewardType : uint32_t enum class XblAchievementRarityCategory : uint32_t { /// - /// The rarity is incalculable (e.g. no one has played the title yet, demoninator is 0). + /// The rarity is incalculable (e.g. no one has played the title yet, denominator is 0). /// Unset = 0, diff --git a/Include/xsapi-c/achievements_manager_c.h b/Include/xsapi-c/achievements_manager_c.h index 853d28b8..531bd757 100644 --- a/Include/xsapi-c/achievements_manager_c.h +++ b/Include/xsapi-c/achievements_manager_c.h @@ -260,7 +260,7 @@ STDAPI XblAchievementsManagerGetAchievementsByState( /// /// The Xbox User ID of the player. /// The UTF-8 encoded achievement ID as defined by Dev Center. -/// The completion percentage of the achievement to indicate progress. +/// The completion percentage of the achievement to indicate progress. /// Valid values are from 1 to 100. Set to 100 to unlock the achievement. /// Progress will be set by the server to the highest value sent /// HRESULT return code for this API operation. @@ -287,7 +287,7 @@ STDAPI XblAchievementsManagerGetAchievementsByState( STDAPI XblAchievementsManagerUpdateAchievement( _In_ uint64_t xboxUserId, _In_ const char* achievementId, - _In_ uint8_t currentProgess + _In_ uint8_t currentProgress ) XBL_NOEXCEPT; } //end extern "C" diff --git a/Include/xsapi-c/multiplayer_c.h b/Include/xsapi-c/multiplayer_c.h index b134a38b..053934c4 100644 --- a/Include/xsapi-c/multiplayer_c.h +++ b/Include/xsapi-c/multiplayer_c.h @@ -323,7 +323,7 @@ enum class XblMultiplayerInitializationStage : uint32_t Evaluating, /// - /// Failed stage. If the first initilization episode didn't succeed, the session can't be initialized. + /// Failed stage. If the first initialization episode didn't succeed, the session can't be initialized. /// Failed }; @@ -1138,7 +1138,7 @@ typedef struct XblMultiplayerSessionConstants XblMultiplayerSessionCapabilities SessionCapabilities; } XblMultiplayerSessionConstants; -#define XBL_MULTIPLAYER_DEVICE_TOKEN_MAX_LENGTH 40 // TODO confirm max size, not a GUID +#define XBL_MULTIPLAYER_DEVICE_TOKEN_MAX_LENGTH 40 #define XBL_MULTIPLAYER_SESSION_TEMPLATE_NAME_MAX_LENGTH 100 #define XBL_MULTIPLAYER_SESSION_NAME_MAX_LENGTH XBL_MULTIPLAYER_SESSION_TEMPLATE_NAME_MAX_LENGTH @@ -2580,7 +2580,7 @@ STDAPI XblMultiplayerSessionSetMutableRoleSettings( /// Gets the collection of members that are in the session or entering the session together. /// /// Handle to the multiplayer session. -/// Passes back a pointer to array of session member ojects. +/// Passes back a pointer to array of session member objects. /// The memory for the returned pointer will remain valid for the life of the XblMultiplayerSessionHandle object until it is closed. /// Passes back the size of the returned array. /// HRESULT return code for this API operation. @@ -3243,7 +3243,7 @@ STDAPI XblMultiplayerSearchHandleGetJoinRestriction( ) XBL_NOEXCEPT; /// -/// Get whether or not the session associated with the search handle is temporaraly closed for joining. +/// Get whether or not the session associated with the search handle is temporarily closed for joining. /// /// Handle to the search handle details. /// Passes back whether the session is closed or not. @@ -3270,7 +3270,7 @@ STDAPI XblMultiplayerSearchHandleGetMemberCounts( /// Get the creation time of the search handle. /// /// Handle to the search handle details. -/// Passes back the time the serach handle was created in MPSD (not the local object). +/// Passes back the time the search handle was created in MPSD (not the local object). /// HRESULT return code for this API operation. STDAPI XblMultiplayerSearchHandleGetCreationTime( _In_ XblMultiplayerSearchHandle handle, @@ -3936,7 +3936,7 @@ STDAPI XblMultiplayerGetActivitiesWithPropertiesForUsersResult( /// HRESULT return code for this API operation. /// /// This method immediately enables the RTA connection, but the in order to receive session changed callbacks, the session -/// must be written again after enabling sunscriptions. +/// must be written again after enabling subscriptions. /// STDAPI XblMultiplayerSetSubscriptionsEnabled( _In_ XblContextHandle xblContext, diff --git a/Include/xsapi-c/multiplayer_manager_c.h b/Include/xsapi-c/multiplayer_manager_c.h index fbf33938..9600afad 100644 --- a/Include/xsapi-c/multiplayer_manager_c.h +++ b/Include/xsapi-c/multiplayer_manager_c.h @@ -1098,7 +1098,7 @@ STDAPI XblMultiplayerManagerLobbySessionSetProperties( /// /// This function sets the value, represented as a JSON string, of a custom property for the lobby session. Custom properties /// can be changed at any time. If custom properties are shared between devices, or may be updated by several devices -/// at the same time, use this function to ensure atomicity and resolve any conflicts between devices while changing the values of those cusotm properties. +/// at the same time, use this function to ensure atomicity and resolve any conflicts between devices while changing the values of those custom properties. /// If a custom property isn't shared across devices, use the function instead /// to change the value of that custom property. /// The service may reject the request to change the custom property if a race condition occurs due to a conflict. @@ -1363,7 +1363,7 @@ STDAPI XblMultiplayerManagerGameSessionSetProperties( /// /// This function sets the value, represented as a JSON string, of a custom property for the game session. Custom properties /// can be changed at any time. If custom properties are shared between devices, or may be updated by several devices -/// at the same time, use this function to ensure atomicity and resolve any conflicts between devices while changing the values of those cusotm properties. +/// at the same time, use this function to ensure atomicity and resolve any conflicts between devices while changing the values of those custom properties. /// If a custom property isn't shared across devices, use the function instead /// to change the value of that custom property. /// The service may reject the request to change the custom property if a race condition occurs due to a conflict. diff --git a/Include/xsapi-c/title_storage_c.h b/Include/xsapi-c/title_storage_c.h index 25f59256..0b7b05ac 100644 --- a/Include/xsapi-c/title_storage_c.h +++ b/Include/xsapi-c/title_storage_c.h @@ -17,7 +17,7 @@ extern "C" #define XBL_TITLE_STORAGE_BLOB_PATH_MAX_LENGTH (257 * 3) #define XBL_TITLE_STORAGE_BLOB_DISPLAY_NAME_MAX_LENGTH (129 * 3) -#define XBL_TITLE_STORAGE_BLOB_ETAG_MAX_LENGTH (18 * 3) // TODO confirm with Azure Blob Services team +#define XBL_TITLE_STORAGE_BLOB_ETAG_MAX_LENGTH (18 * 3) /// /// Defines values used to indicate title storage type. diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 00000000..4f63a653 --- /dev/null +++ b/NuGet.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Source/Services/Leaderboard/leaderboard_result.cpp b/Source/Services/Leaderboard/leaderboard_result.cpp index 719dd0ea..89054ff1 100644 --- a/Source/Services/Leaderboard/leaderboard_result.cpp +++ b/Source/Services/Leaderboard/leaderboard_result.cpp @@ -254,7 +254,14 @@ LeaderboardResult::SerializeQuery(XblLeaderboardQuery* query, char* buffer) query->xboxUserId = m_globalQuery->xuid.empty() ? 0 : utils::internal_string_to_uint64(m_globalQuery->xuid); utils::strcpy(buffer, m_globalQuery->name.size() + 1, m_globalQuery->name.c_str()); - query->leaderboardName = static_cast(buffer); + if (m_globalQuery->isTitleManaged) + { + query->statName = static_cast(buffer); + } + else + { + query->leaderboardName = static_cast(buffer); + } buffer += m_globalQuery->name.size() + 1; m_additionalColumnleaderboardNamesC.resize(m_globalQuery->columns.size()); diff --git a/Source/Services/Multiplayer/Manager/multiplayer_lobby_client.cpp b/Source/Services/Multiplayer/Manager/multiplayer_lobby_client.cpp index 0127eeaf..ff9e3e4a 100644 --- a/Source/Services/Multiplayer/Manager/multiplayer_lobby_client.cpp +++ b/Source/Services/Multiplayer/Manager/multiplayer_lobby_client.cpp @@ -1201,11 +1201,22 @@ HRESULT MultiplayerLobbyClient::CreateGameFromLobby() noexcept } else if(m_setTransferHandleAttempt++ < MAX_CONNECTION_ATTEMPTS) { - m_sessionToCommit = writeSessionResult.ExtractPayload(); - // Retry setting transfer handle after a small delay - HRESULT hr = m_lobbyClient->m_queue.RunWork([op] { - op->SetTransferHandleToPending(); - }, RETRY_DELAY_MS); + + std::shared_ptr sessionToCommitTemp = writeSessionResult.ExtractPayload(); + + HRESULT hr = S_OK; + if (sessionToCommitTemp != nullptr) // handle rare case where an empty body is returned + { + m_sessionToCommit = sessionToCommitTemp; + // Retry setting transfer handle after a small delay + hr = m_lobbyClient->m_queue.RunWork([op] { + op->SetTransferHandleToPending(); + }, RETRY_DELAY_MS); + } + else + { + hr = writeSessionResult.Hresult(); + } if (FAILED(hr)) { diff --git a/Source/Shared/build_version.h b/Source/Shared/build_version.h index 50864a11..529575d4 100644 --- a/Source/Shared/build_version.h +++ b/Source/Shared/build_version.h @@ -9,4 +9,4 @@ //********************************************************* #pragma once -#define XBOX_SERVICES_API_VERSION_STRING "2022.06.20220915.4" +#define XBOX_SERVICES_API_VERSION_STRING "2022.10.20220915.0" diff --git a/Tests/ApiExplorer/APIs/apis_xal.cpp b/Tests/ApiExplorer/APIs/apis_xal.cpp index e79d869a..fd0d8b7d 100644 --- a/Tests/ApiExplorer/APIs/apis_xal.cpp +++ b/Tests/ApiExplorer/APIs/apis_xal.cpp @@ -9,6 +9,8 @@ int XalCleanupAsync_Lua(lua_State *L) if (Data()->nsalMockCall != nullptr) { HCMockRemoveMock(Data()->nsalMockCall); + //Close the handle we're holding onto for API runner state + HCMockCallCloseHandle(Data()->nsalMockCall); Data()->nsalMockCall = nullptr; if (Data()->libHttpClientInit) // Set on GDK where XAL is just a wrapper around XUser diff --git a/Tests/ApiExplorer/APIs/apis_xblc_multiplayer.cpp b/Tests/ApiExplorer/APIs/apis_xblc_multiplayer.cpp index 898bd21c..264339d1 100644 --- a/Tests/ApiExplorer/APIs/apis_xblc_multiplayer.cpp +++ b/Tests/ApiExplorer/APIs/apis_xblc_multiplayer.cpp @@ -83,6 +83,11 @@ int XblMultiplayerSessionCreateHandle_Lua(lua_State *L) // CODE SNIPPET END auto state{ MPState() }; + auto& session{ state->sessionHandles[static_cast(sessionIndex)] }; + if (session) + { + XblMultiplayerSessionCloseHandle(session); + } state->sessionHandles[static_cast(sessionIndex)] = sessionHandle; lua_pushinteger(L, static_cast(sessionIndex)); @@ -1441,6 +1446,13 @@ int XblMultiplayerGetSessionAsync_Lua(lua_State *L) std::unique_ptr sessionIndexPtr{ static_cast(asyncBlock->context) }; auto sessionIndex{ *sessionIndexPtr }; + auto& session{ MPState()->sessionHandles[sessionIndex] }; //CODE SNIP SKIP + if (session) //CODE SNIP SKIP + { + XblMultiplayerSessionCloseHandle(session); //CODE SNIP SKIP + MPState()->sessionHandles[sessionIndex] = nullptr; //CODE SNIP SKIP + } + XblMultiplayerSessionHandle sessionHandle = nullptr; auto hr = XblMultiplayerGetSessionResult(asyncBlock, &sessionHandle); LogToFile("XblMultiplayerGetSessionResult: hr=%s", ConvertHR(hr).c_str()); // CODE SNIP SKIP @@ -1490,6 +1502,13 @@ int XblMultiplayerGetSessionByHandleAsync_Lua(lua_State *L) std::unique_ptr sessionIndexPtr{ static_cast(asyncBlock->context) }; auto sessionIndex{ *sessionIndexPtr }; + auto& session{ MPState()->sessionHandles[sessionIndex] }; //CODE SNIP SKIP + if (session) //CODE SNIP SKIP + { + XblMultiplayerSessionCloseHandle(session); //CODE SNIP SKIP + MPState()->sessionHandles[sessionIndex] = nullptr; //CODE SNIP SKIP + } + XblMultiplayerSessionHandle sessionHandle = nullptr; auto hr = XblMultiplayerGetSessionByHandleResult(asyncBlock, &sessionHandle); LogToFile("XblMultiplayerGetSessionByHandleResult: hr=%s", ConvertHR(hr).c_str()); // CODE SNIP SKIP diff --git a/Tests/ApiExplorer/Tests/mp/mp_partial_invite_flow.lua b/Tests/ApiExplorer/Tests/mp/mp_partial_invite_flow.lua index 7b53a7ce..7e197642 100644 --- a/Tests/ApiExplorer/Tests/mp/mp_partial_invite_flow.lua +++ b/Tests/ApiExplorer/Tests/mp/mp_partial_invite_flow.lua @@ -19,6 +19,7 @@ function OnXblMultiplayerSendInvitesAsync() end function OnXblMultiplayerGetSessionByHandleAsync() + XblMultiplayerSessionCloseHandle() test.stopTest(); end diff --git a/Tests/ApiExplorer/Tests/mp/mp_search.lua b/Tests/ApiExplorer/Tests/mp/mp_search.lua index 3a95b5e7..b8f727d6 100644 --- a/Tests/ApiExplorer/Tests/mp/mp_search.lua +++ b/Tests/ApiExplorer/Tests/mp/mp_search.lua @@ -35,6 +35,7 @@ end function OnXblMultiplayerDeleteSearchHandleAsync() XblMultiplayerSearchHandleCloseHandle() + XblMultiplayerSessionCloseHandle() test.stopTest(); end diff --git a/Tests/ApiExplorer/Tests/mp/mp_transfer.lua b/Tests/ApiExplorer/Tests/mp/mp_transfer.lua index 084388f7..b3c64d78 100644 --- a/Tests/ApiExplorer/Tests/mp/mp_transfer.lua +++ b/Tests/ApiExplorer/Tests/mp/mp_transfer.lua @@ -22,6 +22,8 @@ function OnXblMultiplayerWriteSessionAsync() end function OnXblMultiplayerSetTransferHandleAsync() + XblMultiplayerSessionCloseHandle(session1) + XblMultiplayerSessionCloseHandle(session2) test.stopTest(); end diff --git a/Tests/GDK/APIRunner.GDK/APIRunnerBin-MicrosoftGame.Config b/Tests/GDK/APIRunner.GDK/APIRunnerBin-MicrosoftGame.Config index 388ec697..002521b9 100644 --- a/Tests/GDK/APIRunner.GDK/APIRunnerBin-MicrosoftGame.Config +++ b/Tests/GDK/APIRunner.GDK/APIRunnerBin-MicrosoftGame.Config @@ -1,5 +1,5 @@ - + diff --git a/Tests/GDK/APIRunner.GDK/APIRunnerSrc-MicrosoftGame.Config b/Tests/GDK/APIRunner.GDK/APIRunnerSrc-MicrosoftGame.Config index 862f3c8d..26004718 100644 --- a/Tests/GDK/APIRunner.GDK/APIRunnerSrc-MicrosoftGame.Config +++ b/Tests/GDK/APIRunner.GDK/APIRunnerSrc-MicrosoftGame.Config @@ -1,5 +1,5 @@ - + diff --git a/Tests/GDK/APIRunner.GDK/APIRunnerSrc143-MicrosoftGame.Config b/Tests/GDK/APIRunner.GDK/APIRunnerSrc143-MicrosoftGame.Config index 6feb0d7c..34360b5d 100644 --- a/Tests/GDK/APIRunner.GDK/APIRunnerSrc143-MicrosoftGame.Config +++ b/Tests/GDK/APIRunner.GDK/APIRunnerSrc143-MicrosoftGame.Config @@ -1,5 +1,5 @@ - + diff --git a/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Inc/Keyboard.h b/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Inc/Keyboard.h index 044360c9..499517ae 100644 --- a/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Inc/Keyboard.h +++ b/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Inc/Keyboard.h @@ -1,7 +1,7 @@ //-------------------------------------------------------------------------------------- // File: Keyboard.h // -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // // http://go.microsoft.com/fwlink/?LinkId=248929 @@ -10,10 +10,16 @@ #pragma once +#if (defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)) || (defined(_XBOX_ONE) && defined(_TITLE)) +#ifndef USING_COREWINDOW +#define USING_COREWINDOW +#endif +#endif + #include #include -#if (defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)) || (defined(_XBOX_ONE) && defined(_TITLE)) +#ifdef USING_COREWINDOW namespace ABI { namespace Windows { namespace UI { namespace Core { struct ICoreWindow; } } } } #endif @@ -29,8 +35,9 @@ namespace DirectX { public: Keyboard() noexcept(false); - Keyboard(Keyboard&& moveFrom) noexcept; - Keyboard& operator= (Keyboard&& moveFrom) noexcept; + + Keyboard(Keyboard&&) noexcept; + Keyboard& operator= (Keyboard&&) noexcept; Keyboard(Keyboard const&) = delete; Keyboard& operator=(Keyboard const&) = delete; @@ -39,182 +46,184 @@ namespace DirectX enum Keys : unsigned char { - None = 0, + None = 0, - Back = 0x8, - Tab = 0x9, + Back = 0x8, + Tab = 0x9, - Enter = 0xd, + Enter = 0xd, - Pause = 0x13, - CapsLock = 0x14, - Kana = 0x15, + Pause = 0x13, + CapsLock = 0x14, + Kana = 0x15, + ImeOn = 0x16, - Kanji = 0x19, + Kanji = 0x19, - Escape = 0x1b, - ImeConvert = 0x1c, - ImeNoConvert = 0x1d, + ImeOff = 0x1a, + Escape = 0x1b, + ImeConvert = 0x1c, + ImeNoConvert = 0x1d, - Space = 0x20, - PageUp = 0x21, - PageDown = 0x22, - End = 0x23, - Home = 0x24, - Left = 0x25, - Up = 0x26, - Right = 0x27, - Down = 0x28, - Select = 0x29, - Print = 0x2a, - Execute = 0x2b, - PrintScreen = 0x2c, - Insert = 0x2d, - Delete = 0x2e, - Help = 0x2f, - D0 = 0x30, - D1 = 0x31, - D2 = 0x32, - D3 = 0x33, - D4 = 0x34, - D5 = 0x35, - D6 = 0x36, - D7 = 0x37, - D8 = 0x38, - D9 = 0x39, + Space = 0x20, + PageUp = 0x21, + PageDown = 0x22, + End = 0x23, + Home = 0x24, + Left = 0x25, + Up = 0x26, + Right = 0x27, + Down = 0x28, + Select = 0x29, + Print = 0x2a, + Execute = 0x2b, + PrintScreen = 0x2c, + Insert = 0x2d, + Delete = 0x2e, + Help = 0x2f, + D0 = 0x30, + D1 = 0x31, + D2 = 0x32, + D3 = 0x33, + D4 = 0x34, + D5 = 0x35, + D6 = 0x36, + D7 = 0x37, + D8 = 0x38, + D9 = 0x39, - A = 0x41, - B = 0x42, - C = 0x43, - D = 0x44, - E = 0x45, - F = 0x46, - G = 0x47, - H = 0x48, - I = 0x49, - J = 0x4a, - K = 0x4b, - L = 0x4c, - M = 0x4d, - N = 0x4e, - O = 0x4f, - P = 0x50, - Q = 0x51, - R = 0x52, - S = 0x53, - T = 0x54, - U = 0x55, - V = 0x56, - W = 0x57, - X = 0x58, - Y = 0x59, - Z = 0x5a, - LeftWindows = 0x5b, - RightWindows = 0x5c, - Apps = 0x5d, + A = 0x41, + B = 0x42, + C = 0x43, + D = 0x44, + E = 0x45, + F = 0x46, + G = 0x47, + H = 0x48, + I = 0x49, + J = 0x4a, + K = 0x4b, + L = 0x4c, + M = 0x4d, + N = 0x4e, + O = 0x4f, + P = 0x50, + Q = 0x51, + R = 0x52, + S = 0x53, + T = 0x54, + U = 0x55, + V = 0x56, + W = 0x57, + X = 0x58, + Y = 0x59, + Z = 0x5a, + LeftWindows = 0x5b, + RightWindows = 0x5c, + Apps = 0x5d, - Sleep = 0x5f, - NumPad0 = 0x60, - NumPad1 = 0x61, - NumPad2 = 0x62, - NumPad3 = 0x63, - NumPad4 = 0x64, - NumPad5 = 0x65, - NumPad6 = 0x66, - NumPad7 = 0x67, - NumPad8 = 0x68, - NumPad9 = 0x69, - Multiply = 0x6a, - Add = 0x6b, - Separator = 0x6c, - Subtract = 0x6d, + Sleep = 0x5f, + NumPad0 = 0x60, + NumPad1 = 0x61, + NumPad2 = 0x62, + NumPad3 = 0x63, + NumPad4 = 0x64, + NumPad5 = 0x65, + NumPad6 = 0x66, + NumPad7 = 0x67, + NumPad8 = 0x68, + NumPad9 = 0x69, + Multiply = 0x6a, + Add = 0x6b, + Separator = 0x6c, + Subtract = 0x6d, - Decimal = 0x6e, - Divide = 0x6f, - F1 = 0x70, - F2 = 0x71, - F3 = 0x72, - F4 = 0x73, - F5 = 0x74, - F6 = 0x75, - F7 = 0x76, - F8 = 0x77, - F9 = 0x78, - F10 = 0x79, - F11 = 0x7a, - F12 = 0x7b, - F13 = 0x7c, - F14 = 0x7d, - F15 = 0x7e, - F16 = 0x7f, - F17 = 0x80, - F18 = 0x81, - F19 = 0x82, - F20 = 0x83, - F21 = 0x84, - F22 = 0x85, - F23 = 0x86, - F24 = 0x87, + Decimal = 0x6e, + Divide = 0x6f, + F1 = 0x70, + F2 = 0x71, + F3 = 0x72, + F4 = 0x73, + F5 = 0x74, + F6 = 0x75, + F7 = 0x76, + F8 = 0x77, + F9 = 0x78, + F10 = 0x79, + F11 = 0x7a, + F12 = 0x7b, + F13 = 0x7c, + F14 = 0x7d, + F15 = 0x7e, + F16 = 0x7f, + F17 = 0x80, + F18 = 0x81, + F19 = 0x82, + F20 = 0x83, + F21 = 0x84, + F22 = 0x85, + F23 = 0x86, + F24 = 0x87, - NumLock = 0x90, - Scroll = 0x91, + NumLock = 0x90, + Scroll = 0x91, - LeftShift = 0xa0, - RightShift = 0xa1, - LeftControl = 0xa2, - RightControl = 0xa3, - LeftAlt = 0xa4, - RightAlt = 0xa5, - BrowserBack = 0xa6, - BrowserForward = 0xa7, - BrowserRefresh = 0xa8, - BrowserStop = 0xa9, - BrowserSearch = 0xaa, - BrowserFavorites = 0xab, - BrowserHome = 0xac, - VolumeMute = 0xad, - VolumeDown = 0xae, - VolumeUp = 0xaf, - MediaNextTrack = 0xb0, - MediaPreviousTrack = 0xb1, - MediaStop = 0xb2, - MediaPlayPause = 0xb3, - LaunchMail = 0xb4, - SelectMedia = 0xb5, - LaunchApplication1 = 0xb6, - LaunchApplication2 = 0xb7, + LeftShift = 0xa0, + RightShift = 0xa1, + LeftControl = 0xa2, + RightControl = 0xa3, + LeftAlt = 0xa4, + RightAlt = 0xa5, + BrowserBack = 0xa6, + BrowserForward = 0xa7, + BrowserRefresh = 0xa8, + BrowserStop = 0xa9, + BrowserSearch = 0xaa, + BrowserFavorites = 0xab, + BrowserHome = 0xac, + VolumeMute = 0xad, + VolumeDown = 0xae, + VolumeUp = 0xaf, + MediaNextTrack = 0xb0, + MediaPreviousTrack = 0xb1, + MediaStop = 0xb2, + MediaPlayPause = 0xb3, + LaunchMail = 0xb4, + SelectMedia = 0xb5, + LaunchApplication1 = 0xb6, + LaunchApplication2 = 0xb7, - OemSemicolon = 0xba, - OemPlus = 0xbb, - OemComma = 0xbc, - OemMinus = 0xbd, - OemPeriod = 0xbe, - OemQuestion = 0xbf, - OemTilde = 0xc0, + OemSemicolon = 0xba, + OemPlus = 0xbb, + OemComma = 0xbc, + OemMinus = 0xbd, + OemPeriod = 0xbe, + OemQuestion = 0xbf, + OemTilde = 0xc0, - OemOpenBrackets = 0xdb, - OemPipe = 0xdc, - OemCloseBrackets = 0xdd, - OemQuotes = 0xde, - Oem8 = 0xdf, + OemOpenBrackets = 0xdb, + OemPipe = 0xdc, + OemCloseBrackets = 0xdd, + OemQuotes = 0xde, + Oem8 = 0xdf, - OemBackslash = 0xe2, + OemBackslash = 0xe2, - ProcessKey = 0xe5, + ProcessKey = 0xe5, - OemCopy = 0xf2, - OemAuto = 0xf3, - OemEnlW = 0xf4, + OemCopy = 0xf2, + OemAuto = 0xf3, + OemEnlW = 0xf4, - Attn = 0xf6, - Crsel = 0xf7, - Exsel = 0xf8, - EraseEof = 0xf9, - Play = 0xfa, - Zoom = 0xfb, + Attn = 0xf6, + Crsel = 0xf7, + Exsel = 0xf8, + EraseEof = 0xf9, + Play = 0xfa, + Zoom = 0xfb, - Pa1 = 0xfd, - OemClear = 0xfe, + Pa1 = 0xfd, + OemClear = 0xfe, }; struct State @@ -229,10 +238,11 @@ namespace DirectX bool Pause : 1; // VK_PAUSE, 0x13 bool CapsLock : 1; // VK_CAPITAL, 0x14 bool Kana : 1; // VK_KANA, 0x15 - bool Reserved4 : 2; + bool ImeOn : 1; // VK_IME_ON, 0x16 + bool Reserved4 : 1; bool Reserved5 : 1; bool Kanji : 1; // VK_KANJI, 0x19 - bool Reserved6 : 1; + bool ImeOff : 1; // VK_IME_OFF, 0X1A bool Escape : 1; // VK_ESCAPE, 0x1B bool ImeConvert : 1; // VK_CONVERT, 0x1C bool ImeNoConvert : 1; // VK_NONCONVERT, 0x1D @@ -402,14 +412,14 @@ namespace DirectX bool Reserved25 : 1; bool Pa1 : 1; // VK_PA1, 0xFD bool OemClear : 1; // VK_OEM_CLEAR, 0xFE - bool Reserved26: 1; + bool Reserved26 : 1; bool __cdecl IsKeyDown(Keys key) const noexcept { if (key <= 0xfe) { auto ptr = reinterpret_cast(this); - unsigned int bf = 1u << (key & 0x1f); + const unsigned int bf = 1u << (key & 0x1f); return (ptr[(key >> 5)] & bf) != 0; } return false; @@ -420,7 +430,7 @@ namespace DirectX if (key <= 0xfe) { auto ptr = reinterpret_cast(this); - unsigned int bf = 1u << (key & 0x1f); + const unsigned int bf = 1u << (key & 0x1f); return (ptr[(key >> 5)] & bf) == 0; } return false; @@ -433,7 +443,7 @@ namespace DirectX State released; State pressed; - #pragma prefast(suppress: 26495, "Reset() performs the initialization") + #pragma prefast(suppress: 26495, "Reset() performs the initialization") KeyboardStateTracker() noexcept { Reset(); } void __cdecl Update(const State& state) noexcept; @@ -458,11 +468,7 @@ namespace DirectX // Feature detection bool __cdecl IsConnected() const; - #if (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) && defined(WM_USER) - static void __cdecl ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam); - #endif - - #if (defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)) || (defined(_XBOX_ONE) && defined(_TITLE)) + #ifdef USING_COREWINDOW void __cdecl SetWindow(ABI::Windows::UI::Core::ICoreWindow* window); #ifdef __cplusplus_winrt void __cdecl SetWindow(Windows::UI::Core::CoreWindow^ window) @@ -478,7 +484,9 @@ namespace DirectX SetWindow(reinterpret_cast(winrt::get_abi(window))); } #endif - #endif // WINAPI_FAMILY == WINAPI_FAMILY_APP + #elif defined(WM_USER) + static void __cdecl ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam); + #endif // Singleton static Keyboard& __cdecl Get(); diff --git a/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Inc/Mouse.h b/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Inc/Mouse.h index 51aa3603..2ad4307a 100644 --- a/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Inc/Mouse.h +++ b/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Inc/Mouse.h @@ -1,7 +1,7 @@ //-------------------------------------------------------------------------------------- // File: Mouse.h // -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // // http://go.microsoft.com/fwlink/?LinkId=248929 @@ -10,9 +10,15 @@ #pragma once +#if (defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)) || (defined(_XBOX_ONE) && defined(_TITLE)) +#ifndef USING_COREWINDOW +#define USING_COREWINDOW +#endif +#endif + #include -#if (defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)) || (defined(_XBOX_ONE) && defined(_TITLE) && (_XDK_VER >= 0x42D907D1)) +#ifdef USING_COREWINDOW namespace ABI { namespace Windows { namespace UI { namespace Core { struct ICoreWindow; } } } } #endif @@ -28,8 +34,9 @@ namespace DirectX { public: Mouse() noexcept(false); - Mouse(Mouse&& moveFrom) noexcept; - Mouse& operator= (Mouse&& moveFrom) noexcept; + + Mouse(Mouse&&) noexcept; + Mouse& operator= (Mouse&&) noexcept; Mouse(Mouse const&) = delete; Mouse& operator=(Mouse const&) = delete; @@ -72,7 +79,7 @@ namespace DirectX ButtonState xButton1; ButtonState xButton2; - #pragma prefast(suppress: 26495, "Reset() performs the initialization") + #pragma prefast(suppress: 26495, "Reset() performs the initialization") ButtonStateTracker() noexcept { Reset(); } void __cdecl Update(const State& state) noexcept; @@ -101,17 +108,7 @@ namespace DirectX bool __cdecl IsVisible() const noexcept; void __cdecl SetVisible(bool visible); - #ifdef WM_USER - #if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP) - void __cdecl SetWindow(HWND window); - static void __cdecl ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam); - #elif (WINAPI_FAMILY == WINAPI_FAMILY_GAMES) - static void __cdecl ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam); - static void __cdecl SetResolution(bool use4k); - #endif - #endif - - #if (defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)) || (defined(_XBOX_ONE) && defined(_TITLE) && (_XDK_VER >= 0x42D907D1)) + #ifdef USING_COREWINDOW void __cdecl SetWindow(ABI::Windows::UI::Core::ICoreWindow* window); #ifdef __cplusplus_winrt void __cdecl SetWindow(Windows::UI::Core::CoreWindow^ window) @@ -129,7 +126,14 @@ namespace DirectX #endif static void __cdecl SetDpi(float dpi); - #endif // WINAPI_FAMILY == WINAPI_FAMILY_APP + #elif defined(WM_USER) + void __cdecl SetWindow(HWND window); + static void __cdecl ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam); + + #ifdef _GAMING_XBOX + static void __cdecl SetResolution(float scale); + #endif + #endif // Singleton static Mouse& __cdecl Get(); diff --git a/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Src/Keyboard.cpp b/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Src/Keyboard.cpp index 88439b1b..4f133eda 100644 --- a/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Src/Keyboard.cpp +++ b/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Src/Keyboard.cpp @@ -1,7 +1,7 @@ //-------------------------------------------------------------------------------------- // File: Keyboard.cpp // -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // // http://go.microsoft.com/fwlink/?LinkId=248929 @@ -31,7 +31,7 @@ namespace auto ptr = reinterpret_cast(&state); - unsigned int bf = 1u << (key & 0x1f); + const unsigned int bf = 1u << (key & 0x1f); ptr[(key >> 5)] |= bf; } @@ -42,13 +42,14 @@ namespace auto ptr = reinterpret_cast(&state); - unsigned int bf = 1u << (key & 0x1f); + const unsigned int bf = 1u << (key & 0x1f); ptr[(key >> 5)] &= ~bf; } } -#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_GAMES) +#pragma region Implementations +#if (defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_GAMES)) || (defined(_GAMING_DESKTOP) && (_GRDK_EDITION >= 220600)) #include @@ -67,7 +68,7 @@ public: { if (s_keyboard) { - throw std::exception("Keyboard is a singleton"); + throw std::logic_error("Keyboard is a singleton"); } s_keyboard = this; @@ -96,10 +97,9 @@ public: { if (mGameInput) { - HRESULT hr = mGameInput->UnregisterCallback(mDeviceToken, UINT64_MAX); - if (FAILED(hr)) + if (!mGameInput->UnregisterCallback(mDeviceToken, UINT64_MAX)) { - DebugTrace("ERROR: GameInput::UnregisterCallback [keyboard] failed (%08X)", static_cast(hr)); + DebugTrace("ERROR: GameInput::UnregisterCallback [keyboard] failed"); } } @@ -120,6 +120,18 @@ public: for (size_t j = 0; j < readCount; ++j) { int vk = static_cast(mKeyState[j].virtualKey); + + // Workaround for known issues with VK_RSHIFT and VK_NUMLOCK + if (vk == 0) + { + switch (mKeyState[j].scanCode) + { + case 0xe036: vk = VK_RSHIFT; break; + case 0xe045: vk = VK_NUMLOCK; break; + default: break; + } + } + KeyDown(vk, state); } } @@ -172,150 +184,13 @@ private: Keyboard::Impl* Keyboard::Impl::s_keyboard = nullptr; -#elif !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP) - -//====================================================================================== -// Win32 desktop implementation -//====================================================================================== - -// -// For a Win32 desktop application, call this function from your Window Message Procedure -// -// LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) -// { -// switch (message) -// { -// -// case WM_ACTIVATEAPP: -// Keyboard::ProcessMessage(message, wParam, lParam); -// break; -// -// case WM_KEYDOWN: -// case WM_SYSKEYDOWN: -// case WM_KEYUP: -// case WM_SYSKEYUP: -// Keyboard::ProcessMessage(message, wParam, lParam); -// break; -// -// } -// } -// - -class Keyboard::Impl +void Keyboard::ProcessMessage(UINT, WPARAM, LPARAM) { -public: - Impl(Keyboard* owner) : - mState{}, - mOwner(owner) - { - if (s_keyboard) - { - throw std::exception("Keyboard is a singleton"); - } - - s_keyboard = this; - } - - Impl(Impl&&) = default; - Impl& operator= (Impl&&) = default; - - Impl(Impl const&) = delete; - Impl& operator= (Impl const&) = delete; - - ~Impl() - { - s_keyboard = nullptr; - } - - void GetState(State& state) const - { - memcpy(&state, &mState, sizeof(State)); - } - - void Reset() noexcept - { - memset(&mState, 0, sizeof(State)); - } - - bool IsConnected() const - { - return true; - } - - State mState; - Keyboard* mOwner; - - static Keyboard::Impl* s_keyboard; -}; - - -Keyboard::Impl* Keyboard::Impl::s_keyboard = nullptr; - - -void Keyboard::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam) -{ - auto pImpl = Impl::s_keyboard; - - if (!pImpl) - return; - - bool down = false; - - switch (message) - { - case WM_ACTIVATEAPP: - pImpl->Reset(); - return; - - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - down = true; - break; - - case WM_KEYUP: - case WM_SYSKEYUP: - break; - - default: - return; - } - - int vk = static_cast(wParam); - switch (vk) - { - case VK_SHIFT: - vk = static_cast( - MapVirtualKey((static_cast(lParam) & 0x00ff0000) >> 16u, - MAPVK_VSC_TO_VK_EX)); - if (!down) - { - // Workaround to ensure left vs. right shift get cleared when both were pressed at same time - KeyUp(VK_LSHIFT, pImpl->mState); - KeyUp(VK_RSHIFT, pImpl->mState); - } - break; - - case VK_CONTROL: - vk = (static_cast(lParam) & 0x01000000) ? VK_RCONTROL : VK_LCONTROL; - break; - - case VK_MENU: - vk = (static_cast(lParam) & 0x01000000) ? VK_RMENU : VK_LMENU; - break; - } - - if (down) - { - KeyDown(vk, pImpl->mState); - } - else - { - KeyUp(vk, pImpl->mState); - } + // GameInput for Keyboard doesn't require Win32 messages, but this simplifies integration. } -#else +#elif defined(USING_COREWINDOW) //====================================================================================== // Windows Store or Universal Windows Platform (UWP) app implementation @@ -343,7 +218,7 @@ public: { if (s_keyboard) { - throw std::exception("Keyboard is a singleton"); + throw std::logic_error("Keyboard is a singleton"); } s_keyboard = this; @@ -440,19 +315,19 @@ private: HRESULT hr = mWindow->get_Dispatcher(dispatcher.GetAddressOf()); ThrowIfFailed(hr); - (void)mWindow->remove_Activated(mActivatedToken); + std::ignore = mWindow->remove_Activated(mActivatedToken); mActivatedToken.value = 0; ComPtr keys; hr = dispatcher.As(&keys); ThrowIfFailed(hr); - (void)keys->remove_AcceleratorKeyActivated(mAcceleratorKeyToken); + std::ignore = keys->remove_AcceleratorKeyActivated(mAcceleratorKeyToken); mAcceleratorKeyToken.value = 0; } } - static HRESULT Activated(IInspectable *, ABI::Windows::UI::Core::IWindowActivatedEventArgs*) + static HRESULT Activated(IInspectable*, ABI::Windows::UI::Core::IWindowActivatedEventArgs*) { auto pImpl = Impl::s_keyboard; @@ -464,7 +339,7 @@ private: return S_OK; } - static HRESULT AcceleratorKeyEvent(IInspectable *, ABI::Windows::UI::Core::IAcceleratorKeyEventArgs* args) + static HRESULT AcceleratorKeyEvent(IInspectable*, ABI::Windows::UI::Core::IAcceleratorKeyEventArgs* args) { using namespace ABI::Windows::System; using namespace ABI::Windows::UI::Core; @@ -482,17 +357,17 @@ private: switch (evtType) { - case CoreAcceleratorKeyEventType_KeyDown: - case CoreAcceleratorKeyEventType_SystemKeyDown: - down = true; - break; + case CoreAcceleratorKeyEventType_KeyDown: + case CoreAcceleratorKeyEventType_SystemKeyDown: + down = true; + break; - case CoreAcceleratorKeyEventType_KeyUp: - case CoreAcceleratorKeyEventType_SystemKeyUp: - break; + case CoreAcceleratorKeyEventType_KeyUp: + case CoreAcceleratorKeyEventType_SystemKeyUp: + break; - default: - return S_OK; + default: + return S_OK; } CorePhysicalKeyStatus status; @@ -507,23 +382,23 @@ private: switch (vk) { - case VK_SHIFT: - vk = (status.ScanCode == 0x36) ? VK_RSHIFT : VK_LSHIFT; - if (!down) - { - // Workaround to ensure left vs. right shift get cleared when both were pressed at same time - KeyUp(VK_LSHIFT, pImpl->mState); - KeyUp(VK_RSHIFT, pImpl->mState); - } - break; + case VK_SHIFT: + vk = (status.ScanCode == 0x36) ? VK_RSHIFT : VK_LSHIFT; + if (!down) + { + // Workaround to ensure left vs. right shift get cleared when both were pressed at same time + KeyUp(VK_LSHIFT, pImpl->mState); + KeyUp(VK_RSHIFT, pImpl->mState); + } + break; - case VK_CONTROL: - vk = (status.IsExtendedKey) ? VK_RCONTROL : VK_LCONTROL; - break; + case VK_CONTROL: + vk = (status.IsExtendedKey) ? VK_RCONTROL : VK_LCONTROL; + break; - case VK_MENU: - vk = (status.IsExtendedKey) ? VK_RMENU : VK_LMENU; - break; + case VK_MENU: + vk = (status.IsExtendedKey) ? VK_RMENU : VK_LMENU; + break; } if (down) @@ -548,7 +423,151 @@ void Keyboard::SetWindow(ABI::Windows::UI::Core::ICoreWindow* window) pImpl->SetWindow(window); } + +#else + +//====================================================================================== +// Win32 desktop implementation +//====================================================================================== + +// +// For a Win32 desktop application, call this function from your Window Message Procedure +// +// LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +// { +// switch (message) +// { +// +// case WM_ACTIVATE: +// case WM_ACTIVATEAPP: +// Keyboard::ProcessMessage(message, wParam, lParam); +// break; +// +// case WM_KEYDOWN: +// case WM_SYSKEYDOWN: +// case WM_KEYUP: +// case WM_SYSKEYUP: +// Keyboard::ProcessMessage(message, wParam, lParam); +// break; +// +// } +// } +// + +class Keyboard::Impl +{ +public: + Impl(Keyboard* owner) : + mState{}, + mOwner(owner) + { + if (s_keyboard) + { + throw std::logic_error("Keyboard is a singleton"); + } + + s_keyboard = this; + } + + Impl(Impl&&) = default; + Impl& operator= (Impl&&) = default; + + Impl(Impl const&) = delete; + Impl& operator= (Impl const&) = delete; + + ~Impl() + { + s_keyboard = nullptr; + } + + void GetState(State& state) const + { + memcpy(&state, &mState, sizeof(State)); + } + + void Reset() noexcept + { + memset(&mState, 0, sizeof(State)); + } + + bool IsConnected() const + { + return true; + } + + State mState; + Keyboard* mOwner; + + static Keyboard::Impl* s_keyboard; +}; + + +Keyboard::Impl* Keyboard::Impl::s_keyboard = nullptr; + + +void Keyboard::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + auto pImpl = Impl::s_keyboard; + + if (!pImpl) + return; + + bool down = false; + + switch (message) + { + case WM_ACTIVATE: + case WM_ACTIVATEAPP: + pImpl->Reset(); + return; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + down = true; + break; + + case WM_KEYUP: + case WM_SYSKEYUP: + break; + + default: + return; + } + + int vk = LOWORD(wParam); + // We want to distinguish left and right shift/ctrl/alt keys + switch (vk) + { + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: + { + if (vk == VK_SHIFT && !down) + { + // Workaround to ensure left vs. right shift get cleared when both were pressed at same time + KeyUp(VK_LSHIFT, pImpl->mState); + KeyUp(VK_RSHIFT, pImpl->mState); + } + + bool isExtendedKey = (HIWORD(lParam) & KF_EXTENDED) == KF_EXTENDED; + int scanCode = LOBYTE(HIWORD(lParam)) | (isExtendedKey ? 0xe000 : 0); + vk = LOWORD(MapVirtualKeyW(static_cast(scanCode), MAPVK_VSC_TO_VK_EX)); + } + break; + } + + if (down) + { + KeyDown(vk, pImpl->mState); + } + else + { + KeyUp(vk, pImpl->mState); + } +} + #endif +#pragma endregion #pragma warning( disable : 4355 ) @@ -577,9 +596,7 @@ Keyboard& Keyboard::operator= (Keyboard&& moveFrom) noexcept // Public destructor. -Keyboard::~Keyboard() -{ -} +Keyboard::~Keyboard() = default; Keyboard::State Keyboard::GetState() const @@ -604,7 +621,7 @@ bool Keyboard::IsConnected() const Keyboard& Keyboard::Get() { if (!Impl::s_keyboard || !Impl::s_keyboard->mOwner) - throw std::exception("Keyboard is a singleton"); + throw std::logic_error("Keyboard singleton not created"); return *Impl::s_keyboard->mOwner; } @@ -635,7 +652,6 @@ void Keyboard::KeyboardStateTracker::Update(const State& state) noexcept lastState = state; } - void Keyboard::KeyboardStateTracker::Reset() noexcept { memset(this, 0, sizeof(KeyboardStateTracker)); diff --git a/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Src/Mouse.cpp b/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Src/Mouse.cpp index 96f59743..480ba50a 100644 --- a/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Src/Mouse.cpp +++ b/Tests/GDK/APIRunner.GDK/Kits/DirectXTK12/Src/Mouse.cpp @@ -1,7 +1,7 @@ //-------------------------------------------------------------------------------------- // File: Mouse.cpp // -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // // http://go.microsoft.com/fwlink/?LinkId=248929 @@ -16,8 +16,8 @@ using namespace DirectX; using Microsoft::WRL::ComPtr; - -#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_GAMES) +#pragma region Implementations +#if (defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_GAMES)) || (defined(_GAMING_DESKTOP) && (_GRDK_EDITION >= 220600)) #include @@ -32,6 +32,7 @@ using Microsoft::WRL::ComPtr; // { // switch (message) // { +// case WM_ACTIVATE: // case WM_ACTIVATEAPP: // case WM_MOUSEMOVE: // case WM_LBUTTONDOWN: @@ -56,9 +57,10 @@ public: explicit Impl(Mouse* owner) noexcept(false) : mState{}, mOwner(owner), - mIs4k(false), + mScale(1.f), mConnected(0), mDeviceToken(0), + mWindow(nullptr), mMode(MODE_ABSOLUTE), mScrollWheelCurrent(0), mRelativeX(INT64_MAX), @@ -67,7 +69,7 @@ public: { if (s_mouse) { - throw std::exception("Mouse is a singleton"); + throw std::logic_error("Mouse is a singleton"); } s_mouse = this; @@ -86,7 +88,7 @@ public: mScrollWheelValue.reset(CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE)); if (!mScrollWheelValue) { - throw std::exception("CreateEventEx"); + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "CreateEventEx"); } } @@ -102,10 +104,9 @@ public: { if (mGameInput) { - HRESULT hr = mGameInput->UnregisterCallback(mDeviceToken, UINT64_MAX); - if (FAILED(hr)) + if (!mGameInput->UnregisterCallback(mDeviceToken, UINT64_MAX)) { - DebugTrace("ERROR: GameInput::UnregisterCallback [mouse] failed (%08X)", static_cast(hr)); + DebugTrace("ERROR: GameInput::UnregisterCallback [mouse] failed"); } } @@ -122,7 +123,7 @@ public: DWORD result = WaitForSingleObjectEx(mScrollWheelValue.get(), 0, FALSE); if (result == WAIT_FAILED) - throw std::exception("WaitForSingleObjectEx"); + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "WaitForSingleObjectEx"); if (result == WAIT_OBJECT_0) { @@ -168,6 +169,11 @@ public: SetEvent(mScrollWheelValue.get()); } + void SetWindow(HWND window) + { + mWindow = window; + } + void SetMode(Mode mode) { if (mMode == mode) @@ -178,7 +184,18 @@ public: mRelativeY = INT64_MAX; mRelativeWheelY = INT64_MAX; - ShowCursor((mode == MODE_ABSOLUTE) ? TRUE : FALSE); + if (mode == MODE_RELATIVE) + { + ShowCursor(FALSE); + ClipToWindow(); + } + else + { + ShowCursor(TRUE); +#ifndef _GAMING_XBOX + ClipCursor(nullptr); +#endif + } } bool IsConnected() const noexcept @@ -206,7 +223,7 @@ public: CURSORINFO info = { sizeof(CURSORINFO), 0, nullptr, {} }; if (!GetCursorInfo(&info)) { - throw std::exception("GetCursorInfo"); + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "GetCursorInfo"); } bool isvisible = (info.flags & CURSOR_SHOWING) != 0; @@ -218,7 +235,7 @@ public: State mState; Mouse* mOwner; - bool mIs4k; + float mScale; uint32_t mConnected; static Mouse::Impl* s_mouse; @@ -227,6 +244,7 @@ private: ComPtr mGameInput; GameInputCallbackToken mDeviceToken; + HWND mWindow; Mode mMode; ScopedHandle mScrollWheelValue; @@ -256,6 +274,35 @@ private: --impl->mConnected; } } + + void ClipToWindow() noexcept + { +#ifndef _GAMING_XBOX + assert(mWindow != nullptr); + + RECT rect; + GetClientRect(mWindow, &rect); + + POINT ul; + ul.x = rect.left; + ul.y = rect.top; + + POINT lr; + lr.x = rect.right; + lr.y = rect.bottom; + + std::ignore = MapWindowPoints(mWindow, nullptr, &ul, 1); + std::ignore = MapWindowPoints(mWindow, nullptr, &lr, 1); + + rect.left = ul.x; + rect.top = ul.y; + + rect.right = lr.x; + rect.bottom = lr.y; + + ClipCursor(&rect); +#endif + } }; @@ -271,7 +318,7 @@ void Mouse::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam) DWORD result = WaitForSingleObjectEx(pImpl->mScrollWheelValue.get(), 0, FALSE); if (result == WAIT_FAILED) - throw std::exception("WaitForSingleObjectEx"); + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "WaitForSingleObjectEx"); if (result == WAIT_OBJECT_0) { @@ -280,6 +327,7 @@ void Mouse::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam) switch (message) { + case WM_ACTIVATE: case WM_ACTIVATEAPP: if (wParam) { @@ -289,6 +337,14 @@ void Mouse::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam) pImpl->mRelativeY = INT64_MAX; ShowCursor(FALSE); + + pImpl->ClipToWindow(); + } + else + { +#ifndef _GAMING_XBOX + ClipCursor(nullptr); +#endif } } else @@ -368,281 +424,22 @@ void Mouse::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam) int xPos = static_cast(LOWORD(lParam)); // GET_X_LPARAM(lParam); int yPos = static_cast(HIWORD(lParam)); // GET_Y_LPARAM(lParam); - if (pImpl->mIs4k) - { - pImpl->mState.x = static_cast(xPos) * 2; - pImpl->mState.y = static_cast(yPos) * 2; - } - else - { - pImpl->mState.x = static_cast(xPos); - pImpl->mState.y = static_cast(yPos); - } + pImpl->mState.x = static_cast(static_cast(xPos) * pImpl->mScale); + pImpl->mState.y = static_cast(static_cast(yPos) * pImpl->mScale); } } - -void Mouse::SetResolution(bool use4k) +#ifdef _GAMING_XBOX +void Mouse::SetResolution(float scale) { auto pImpl = Impl::s_mouse; if (!pImpl) return; - pImpl->mIs4k = use4k; + pImpl->mScale = scale; } - - -#elif !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP) - -//====================================================================================== -// Win32 desktop implementation -//====================================================================================== - -// -// For a Win32 desktop application, in your window setup be sure to call this method: -// -// m_mouse->SetWindow(hwnd); -// -// And call this static function from your Window Message Procedure -// -// LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) -// { -// switch (message) -// { -// case WM_ACTIVATEAPP: -// case WM_INPUT: -// case WM_MOUSEMOVE: -// case WM_LBUTTONDOWN: -// case WM_LBUTTONUP: -// case WM_RBUTTONDOWN: -// case WM_RBUTTONUP: -// case WM_MBUTTONDOWN: -// case WM_MBUTTONUP: -// case WM_MOUSEWHEEL: -// case WM_XBUTTONDOWN: -// case WM_XBUTTONUP: -// case WM_MOUSEHOVER: -// Mouse::ProcessMessage(message, wParam, lParam); -// break; -// -// } -// } -// - -class Mouse::Impl -{ -public: - explicit Impl(Mouse* owner) noexcept(false) : - mState{}, - mOwner(owner), - mWindow(nullptr), - mMode(MODE_ABSOLUTE), - mLastX(0), - mLastY(0), - mRelativeX(INT32_MAX), - mRelativeY(INT32_MAX), - mInFocus(true) - { - if (s_mouse) - { - throw std::exception("Mouse is a singleton"); - } - - s_mouse = this; - - mScrollWheelValue.reset(CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE)); - mRelativeRead.reset(CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE)); - mAbsoluteMode.reset(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE)); - mRelativeMode.reset(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE)); - if (!mScrollWheelValue - || !mRelativeRead - || !mAbsoluteMode - || !mRelativeMode) - { - throw std::exception("CreateEventEx"); - } - } - - Impl(Impl&&) = default; - Impl& operator= (Impl&&) = default; - - Impl(Impl const&) = delete; - Impl& operator= (Impl const&) = delete; - - ~Impl() - { - s_mouse = nullptr; - } - - void GetState(State& state) const - { - memcpy(&state, &mState, sizeof(State)); - state.positionMode = mMode; - - DWORD result = WaitForSingleObjectEx(mScrollWheelValue.get(), 0, FALSE); - if (result == WAIT_FAILED) - throw std::exception("WaitForSingleObjectEx"); - - if (result == WAIT_OBJECT_0) - { - state.scrollWheelValue = 0; - } - - if (state.positionMode == MODE_RELATIVE) - { - result = WaitForSingleObjectEx(mRelativeRead.get(), 0, FALSE); - - if (result == WAIT_FAILED) - throw std::exception("WaitForSingleObjectEx"); - - if (result == WAIT_OBJECT_0) - { - state.x = 0; - state.y = 0; - } - else - { - SetEvent(mRelativeRead.get()); - } - } - } - - void ResetScrollWheelValue() noexcept - { - SetEvent(mScrollWheelValue.get()); - } - - void SetMode(Mode mode) - { - if (mMode == mode) - return; - - SetEvent((mode == MODE_ABSOLUTE) ? mAbsoluteMode.get() : mRelativeMode.get()); - - assert(mWindow != nullptr); - - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(tme); - tme.dwFlags = TME_HOVER; - tme.hwndTrack = mWindow; - tme.dwHoverTime = 1; - if (!TrackMouseEvent(&tme)) - { - throw std::exception("TrackMouseEvent"); - } - } - - bool IsConnected() const noexcept - { - return GetSystemMetrics(SM_MOUSEPRESENT) != 0; - } - - bool IsVisible() const noexcept - { - if (mMode == MODE_RELATIVE) - return false; - - CURSORINFO info = { sizeof(CURSORINFO), 0, nullptr, {} }; - if (!GetCursorInfo(&info)) - return false; - - return (info.flags & CURSOR_SHOWING) != 0; - } - - void SetVisible(bool visible) - { - if (mMode == MODE_RELATIVE) - return; - - CURSORINFO info = { sizeof(CURSORINFO), 0, nullptr, {} }; - if (!GetCursorInfo(&info)) - { - throw std::exception("GetCursorInfo"); - } - - bool isvisible = (info.flags & CURSOR_SHOWING) != 0; - if (isvisible != visible) - { - ShowCursor(visible); - } - } - - void SetWindow(HWND window) - { - if (mWindow == window) - return; - - assert(window != nullptr); - - RAWINPUTDEVICE Rid; - Rid.usUsagePage = 0x1 /* HID_USAGE_PAGE_GENERIC */; - Rid.usUsage = 0x2 /* HID_USAGE_GENERIC_MOUSE */; - Rid.dwFlags = RIDEV_INPUTSINK; - Rid.hwndTarget = window; - if (!RegisterRawInputDevices(&Rid, 1, sizeof(RAWINPUTDEVICE))) - { - throw std::exception("RegisterRawInputDevices"); - } - - mWindow = window; - } - - State mState; - - Mouse* mOwner; - - static Mouse::Impl* s_mouse; - -private: - HWND mWindow; - Mode mMode; - - ScopedHandle mScrollWheelValue; - ScopedHandle mRelativeRead; - ScopedHandle mAbsoluteMode; - ScopedHandle mRelativeMode; - - int mLastX; - int mLastY; - int mRelativeX; - int mRelativeY; - - bool mInFocus; - - friend void Mouse::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam); - - void ClipToWindow() noexcept - { - assert(mWindow != nullptr); - - RECT rect; - GetClientRect(mWindow, &rect); - - POINT ul; - ul.x = rect.left; - ul.y = rect.top; - - POINT lr; - lr.x = rect.right; - lr.y = rect.bottom; - - MapWindowPoints(mWindow, nullptr, &ul, 1); - MapWindowPoints(mWindow, nullptr, &lr, 1); - - rect.left = ul.x; - rect.top = ul.y; - - rect.right = lr.x; - rect.bottom = lr.y; - - ClipCursor(&rect); - } -}; - - -Mouse::Impl* Mouse::Impl::s_mouse = nullptr; - +#endif void Mouse::SetWindow(HWND window) { @@ -650,278 +447,7 @@ void Mouse::SetWindow(HWND window) } -void Mouse::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam) -{ - auto pImpl = Impl::s_mouse; - - if (!pImpl) - return; - - HANDLE events[3] = { pImpl->mScrollWheelValue.get(), pImpl->mAbsoluteMode.get(), pImpl->mRelativeMode.get() }; - switch (WaitForMultipleObjectsEx(_countof(events), events, FALSE, 0, FALSE)) - { - default: - case WAIT_TIMEOUT: - break; - - case WAIT_OBJECT_0: - pImpl->mState.scrollWheelValue = 0; - ResetEvent(events[0]); - break; - - case (WAIT_OBJECT_0 + 1): - { - pImpl->mMode = MODE_ABSOLUTE; - ClipCursor(nullptr); - - POINT point; - point.x = pImpl->mLastX; - point.y = pImpl->mLastY; - - // We show the cursor before moving it to support Remote Desktop - ShowCursor(TRUE); - - if (MapWindowPoints(pImpl->mWindow, nullptr, &point, 1)) - { - SetCursorPos(point.x, point.y); - } - pImpl->mState.x = pImpl->mLastX; - pImpl->mState.y = pImpl->mLastY; - } - break; - - case (WAIT_OBJECT_0 + 2): - { - ResetEvent(pImpl->mRelativeRead.get()); - - pImpl->mMode = MODE_RELATIVE; - pImpl->mState.x = pImpl->mState.y = 0; - pImpl->mRelativeX = INT32_MAX; - pImpl->mRelativeY = INT32_MAX; - - ShowCursor(FALSE); - - pImpl->ClipToWindow(); - } - break; - - case WAIT_FAILED: - throw std::exception("WaitForMultipleObjectsEx"); - } - - switch (message) - { - case WM_ACTIVATEAPP: - if (wParam) - { - pImpl->mInFocus = true; - - if (pImpl->mMode == MODE_RELATIVE) - { - pImpl->mState.x = pImpl->mState.y = 0; - - ShowCursor(FALSE); - - pImpl->ClipToWindow(); - } - } - else - { - int scrollWheel = pImpl->mState.scrollWheelValue; - memset(&pImpl->mState, 0, sizeof(State)); - pImpl->mState.scrollWheelValue = scrollWheel; - - pImpl->mInFocus = false; - } - return; - - case WM_INPUT: - if (pImpl->mInFocus && pImpl->mMode == MODE_RELATIVE) - { - RAWINPUT raw; - UINT rawSize = sizeof(raw); - - UINT resultData = GetRawInputData(reinterpret_cast(lParam), RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER)); - if (resultData == UINT(-1)) - { - throw std::exception("GetRawInputData"); - } - - if (raw.header.dwType == RIM_TYPEMOUSE) - { - if (!(raw.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE)) - { - pImpl->mState.x = raw.data.mouse.lLastX; - pImpl->mState.y = raw.data.mouse.lLastY; - - ResetEvent(pImpl->mRelativeRead.get()); - } - else if (raw.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) - { - // This is used to make Remote Desktop sessons work - const int width = GetSystemMetrics(SM_CXVIRTUALSCREEN); - const int height = GetSystemMetrics(SM_CYVIRTUALSCREEN); - - int x = static_cast((float(raw.data.mouse.lLastX) / 65535.0f) * float(width)); - int y = static_cast((float(raw.data.mouse.lLastY) / 65535.0f) * float(height)); - - if (pImpl->mRelativeX == INT32_MAX) - { - pImpl->mState.x = pImpl->mState.y = 0; - } - else - { - pImpl->mState.x = x - pImpl->mRelativeX; - pImpl->mState.y = y - pImpl->mRelativeY; - } - - pImpl->mRelativeX = x; - pImpl->mRelativeY = y; - - ResetEvent(pImpl->mRelativeRead.get()); - } - } - } - return; - - case WM_MOUSEMOVE: - break; - - case WM_LBUTTONDOWN: - pImpl->mState.leftButton = true; - break; - - case WM_LBUTTONUP: - pImpl->mState.leftButton = false; - break; - - case WM_RBUTTONDOWN: - pImpl->mState.rightButton = true; - break; - - case WM_RBUTTONUP: - pImpl->mState.rightButton = false; - break; - - case WM_MBUTTONDOWN: - pImpl->mState.middleButton = true; - break; - - case WM_MBUTTONUP: - pImpl->mState.middleButton = false; - break; - - case WM_MOUSEWHEEL: - pImpl->mState.scrollWheelValue += GET_WHEEL_DELTA_WPARAM(wParam); - return; - - case WM_XBUTTONDOWN: - switch (GET_XBUTTON_WPARAM(wParam)) - { - case XBUTTON1: - pImpl->mState.xButton1 = true; - break; - - case XBUTTON2: - pImpl->mState.xButton2 = true; - break; - } - break; - - case WM_XBUTTONUP: - switch (GET_XBUTTON_WPARAM(wParam)) - { - case XBUTTON1: - pImpl->mState.xButton1 = false; - break; - - case XBUTTON2: - pImpl->mState.xButton2 = false; - break; - } - break; - - case WM_MOUSEHOVER: - break; - - default: - // Not a mouse message, so exit - return; - } - - if (pImpl->mMode == MODE_ABSOLUTE) - { - // All mouse messages provide a new pointer position - int xPos = static_cast(LOWORD(lParam)); // GET_X_LPARAM(lParam); - int yPos = static_cast(HIWORD(lParam)); // GET_Y_LPARAM(lParam); - - pImpl->mState.x = pImpl->mLastX = xPos; - pImpl->mState.y = pImpl->mLastY = yPos; - } -} - - -#elif defined(_XBOX_ONE) && (!defined(_TITLE) || (_XDK_VER < 0x42D907D1)) - -//====================================================================================== -// Null device -//====================================================================================== - -class Mouse::Impl -{ -public: - explicit Impl(Mouse* owner) noexcept(false) : - mOwner(owner) - { - if (s_mouse) - { - throw std::exception("Mouse is a singleton"); - } - - s_mouse = this; - } - - ~Impl() - { - s_mouse = nullptr; - } - - void GetState(State& state) const - { - memset(&state, 0, sizeof(State)); - } - - void ResetScrollWheelValue() noexcept - { - } - - void SetMode(Mode) - { - } - - bool IsConnected() const - { - return false; - } - - bool IsVisible() const noexcept - { - return false; - } - - void SetVisible(bool) - { - } - - Mouse* mOwner; - - static Mouse::Impl* s_mouse; -}; - -Mouse::Impl* Mouse::Impl::s_mouse = nullptr; - - -#else +#elif defined(USING_COREWINDOW) //====================================================================================== // Windows Store or Universal Windows Platform (UWP) app implementation @@ -960,7 +486,7 @@ public: { if (s_mouse) { - throw std::exception("Mouse is a singleton"); + throw std::logic_error("Mouse is a singleton"); } s_mouse = this; @@ -970,7 +496,7 @@ public: if (!mScrollWheelValue || !mRelativeRead) { - throw std::exception("CreateEventEx"); + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "CreateEventEx"); } } @@ -987,7 +513,7 @@ public: DWORD result = WaitForSingleObjectEx(mScrollWheelValue.get(), 0, FALSE); if (result == WAIT_FAILED) - throw std::exception("WaitForSingleObjectEx"); + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "WaitForSingleObjectEx"); if (result == WAIT_OBJECT_0) { @@ -999,7 +525,7 @@ public: result = WaitForSingleObjectEx(mRelativeRead.get(), 0, FALSE); if (result == WAIT_FAILED) - throw std::exception("WaitForSingleObjectEx"); + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "WaitForSingleObjectEx"); if (result == WAIT_OBJECT_0) { @@ -1100,7 +626,7 @@ public: if (FAILED(mWindow->get_PointerCursor(cursor.GetAddressOf()))) return false; - return cursor != 0; + return cursor != nullptr; } void SetVisible(bool visible) @@ -1183,7 +709,7 @@ public: } State mState; - Mouse* mOwner; + Mouse* mOwner; float mDPI; static Mouse::Impl* s_mouse; @@ -1208,27 +734,27 @@ private: { if (mWindow) { - (void)mWindow->remove_PointerPressed(mPointerPressedToken); + std::ignore = mWindow->remove_PointerPressed(mPointerPressedToken); mPointerPressedToken.value = 0; - (void)mWindow->remove_PointerReleased(mPointerReleasedToken); + std::ignore = mWindow->remove_PointerReleased(mPointerReleasedToken); mPointerReleasedToken.value = 0; - (void)mWindow->remove_PointerMoved(mPointerMovedToken); + std::ignore = mWindow->remove_PointerMoved(mPointerMovedToken); mPointerMovedToken.value = 0; - (void)mWindow->remove_PointerWheelChanged(mPointerWheelToken); + std::ignore = mWindow->remove_PointerWheelChanged(mPointerWheelToken); mPointerWheelToken.value = 0; } if (mMouse) { - (void)mMouse->remove_MouseMoved(mPointerMouseMovedToken); + std::ignore = mMouse->remove_MouseMoved(mPointerMouseMovedToken); mPointerMouseMovedToken.value = 0; } } - static HRESULT PointerEvent(IInspectable *, ABI::Windows::UI::Core::IPointerEventArgs*args) + static HRESULT PointerEvent(IInspectable*, ABI::Windows::UI::Core::IPointerEventArgs* args) { using namespace ABI::Windows::Foundation; using namespace ABI::Windows::UI::Input; @@ -1292,7 +818,7 @@ private: return S_OK; } - static HRESULT PointerWheel(IInspectable *, ABI::Windows::UI::Core::IPointerEventArgs*args) + static HRESULT PointerWheel(IInspectable*, ABI::Windows::UI::Core::IPointerEventArgs* args) { using namespace ABI::Windows::Foundation; using namespace ABI::Windows::UI::Input; @@ -1319,6 +845,15 @@ private: hr = currentPoint->get_Properties(props.GetAddressOf()); ThrowIfFailed(hr); + boolean ishorz; + hr = props->get_IsHorizontalMouseWheel(&ishorz); + ThrowIfFailed(hr); + if (ishorz) + { + // Mouse only exposes the vertical scroll wheel. + return S_OK; + } + INT32 value; hr = props->get_MouseWheelDelta(&value); ThrowIfFailed(hr); @@ -1348,7 +883,7 @@ private: return S_OK; } - static HRESULT MouseMovedEvent(IInspectable *, ABI::Windows::Devices::Input::IMouseEventArgs* args) + static HRESULT MouseMovedEvent(IInspectable*, ABI::Windows::Devices::Input::IMouseEventArgs* args) { using namespace ABI::Windows::Devices::Input; @@ -1391,7 +926,496 @@ void Mouse::SetDpi(float dpi) pImpl->mDPI = dpi; } + +#else + +//====================================================================================== +// Win32 desktop implementation +//====================================================================================== + +// +// For a Win32 desktop application, in your window setup be sure to call this method: +// +// m_mouse->SetWindow(hwnd); +// +// And call this static function from your Window Message Procedure +// +// LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +// { +// switch (message) +// { +// case WM_ACTIVATE: +// case WM_ACTIVATEAPP: +// case WM_INPUT: +// case WM_MOUSEMOVE: +// case WM_LBUTTONDOWN: +// case WM_LBUTTONUP: +// case WM_RBUTTONDOWN: +// case WM_RBUTTONUP: +// case WM_MBUTTONDOWN: +// case WM_MBUTTONUP: +// case WM_MOUSEWHEEL: +// case WM_XBUTTONDOWN: +// case WM_XBUTTONUP: +// case WM_MOUSEHOVER: +// Mouse::ProcessMessage(message, wParam, lParam); +// break; +// +// } +// } +// + +class Mouse::Impl +{ +public: + explicit Impl(Mouse* owner) noexcept(false) : + mState{}, + mOwner(owner), + mWindow(nullptr), + mMode(MODE_ABSOLUTE), + mLastX(0), + mLastY(0), + mRelativeX(INT32_MAX), + mRelativeY(INT32_MAX), + mInFocus(true) + { + if (s_mouse) + { + throw std::logic_error("Mouse is a singleton"); + } + + s_mouse = this; + + mScrollWheelValue.reset(CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE)); + mRelativeRead.reset(CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE)); + mAbsoluteMode.reset(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE)); + mRelativeMode.reset(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE)); + if (!mScrollWheelValue + || !mRelativeRead + || !mAbsoluteMode + || !mRelativeMode) + { + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "CreateEventEx"); + } + } + + Impl(Impl&&) = default; + Impl& operator= (Impl&&) = default; + + Impl(Impl const&) = delete; + Impl& operator= (Impl const&) = delete; + + ~Impl() + { + s_mouse = nullptr; + } + + void GetState(State& state) const + { + memcpy(&state, &mState, sizeof(State)); + state.positionMode = mMode; + + DWORD result = WaitForSingleObjectEx(mScrollWheelValue.get(), 0, FALSE); + if (result == WAIT_FAILED) + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "WaitForSingleObjectEx"); + + if (result == WAIT_OBJECT_0) + { + state.scrollWheelValue = 0; + } + + if (state.positionMode == MODE_RELATIVE) + { + result = WaitForSingleObjectEx(mRelativeRead.get(), 0, FALSE); + + if (result == WAIT_FAILED) + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "WaitForSingleObjectEx"); + + if (result == WAIT_OBJECT_0) + { + state.x = 0; + state.y = 0; + } + else + { + SetEvent(mRelativeRead.get()); + } + } + } + + void ResetScrollWheelValue() noexcept + { + SetEvent(mScrollWheelValue.get()); + } + + void SetMode(Mode mode) + { + if (mMode == mode) + return; + + SetEvent((mode == MODE_ABSOLUTE) ? mAbsoluteMode.get() : mRelativeMode.get()); + + assert(mWindow != nullptr); + + // Send a WM_HOVER as a way to 'kick' the message processing even if the mouse is still. + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_HOVER; + tme.hwndTrack = mWindow; + tme.dwHoverTime = 1; + if (!TrackMouseEvent(&tme)) + { + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "TrackMouseEvent"); + } + } + + bool IsConnected() const noexcept + { + return GetSystemMetrics(SM_MOUSEPRESENT) != 0; + } + + bool IsVisible() const noexcept + { + if (mMode == MODE_RELATIVE) + return false; + + CURSORINFO info = { sizeof(CURSORINFO), 0, nullptr, {} }; + if (!GetCursorInfo(&info)) + return false; + + return (info.flags & CURSOR_SHOWING) != 0; + } + + void SetVisible(bool visible) + { + if (mMode == MODE_RELATIVE) + return; + + CURSORINFO info = { sizeof(CURSORINFO), 0, nullptr, {} }; + if (!GetCursorInfo(&info)) + { + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "GetCursorInfo"); + } + + const bool isvisible = (info.flags & CURSOR_SHOWING) != 0; + if (isvisible != visible) + { + ShowCursor(visible); + } + } + + void SetWindow(HWND window) + { + if (mWindow == window) + return; + + assert(window != nullptr); + + RAWINPUTDEVICE Rid; + Rid.usUsagePage = 0x1 /* HID_USAGE_PAGE_GENERIC */; + Rid.usUsage = 0x2 /* HID_USAGE_GENERIC_MOUSE */; + Rid.dwFlags = RIDEV_INPUTSINK; + Rid.hwndTarget = window; + if (!RegisterRawInputDevices(&Rid, 1, sizeof(RAWINPUTDEVICE))) + { + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "RegisterRawInputDevices"); + } + + mWindow = window; + } + + State mState; + + Mouse* mOwner; + + static Mouse::Impl* s_mouse; + +private: + HWND mWindow; + Mode mMode; + + ScopedHandle mScrollWheelValue; + ScopedHandle mRelativeRead; + ScopedHandle mAbsoluteMode; + ScopedHandle mRelativeMode; + + int mLastX; + int mLastY; + int mRelativeX; + int mRelativeY; + + bool mInFocus; + + friend void Mouse::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam); + + void ClipToWindow() noexcept + { + assert(mWindow != nullptr); + + RECT rect; + GetClientRect(mWindow, &rect); + + POINT ul; + ul.x = rect.left; + ul.y = rect.top; + + POINT lr; + lr.x = rect.right; + lr.y = rect.bottom; + + std::ignore = MapWindowPoints(mWindow, nullptr, &ul, 1); + std::ignore = MapWindowPoints(mWindow, nullptr, &lr, 1); + + rect.left = ul.x; + rect.top = ul.y; + + rect.right = lr.x; + rect.bottom = lr.y; + + ClipCursor(&rect); + } +}; + + +Mouse::Impl* Mouse::Impl::s_mouse = nullptr; + + +void Mouse::SetWindow(HWND window) +{ + pImpl->SetWindow(window); +} + + +void Mouse::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + auto pImpl = Impl::s_mouse; + + if (!pImpl) + return; + + // First handle any pending scroll wheel reset event. + switch (WaitForSingleObjectEx(pImpl->mScrollWheelValue.get(), 0, FALSE)) + { + default: + case WAIT_TIMEOUT: + break; + + case WAIT_OBJECT_0: + pImpl->mState.scrollWheelValue = 0; + ResetEvent(pImpl->mScrollWheelValue.get()); + break; + + case WAIT_FAILED: + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "WaitForMultipleObjectsEx"); + } + + // Next handle mode change events. + HANDLE events[2] = { pImpl->mAbsoluteMode.get(), pImpl->mRelativeMode.get() }; + switch (WaitForMultipleObjectsEx(static_cast(std::size(events)), events, FALSE, 0, FALSE)) + { + default: + case WAIT_TIMEOUT: + break; + + case WAIT_OBJECT_0: + { + pImpl->mMode = MODE_ABSOLUTE; + ClipCursor(nullptr); + + POINT point; + point.x = pImpl->mLastX; + point.y = pImpl->mLastY; + + // We show the cursor before moving it to support Remote Desktop + ShowCursor(TRUE); + + if (MapWindowPoints(pImpl->mWindow, nullptr, &point, 1)) + { + SetCursorPos(point.x, point.y); + } + pImpl->mState.x = pImpl->mLastX; + pImpl->mState.y = pImpl->mLastY; + } + break; + + case (WAIT_OBJECT_0 + 1): + { + ResetEvent(pImpl->mRelativeRead.get()); + + pImpl->mMode = MODE_RELATIVE; + pImpl->mState.x = pImpl->mState.y = 0; + pImpl->mRelativeX = INT32_MAX; + pImpl->mRelativeY = INT32_MAX; + + ShowCursor(FALSE); + + pImpl->ClipToWindow(); + } + break; + + case WAIT_FAILED: + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "WaitForMultipleObjectsEx"); + } + + switch (message) + { + case WM_ACTIVATE: + case WM_ACTIVATEAPP: + if (wParam) + { + pImpl->mInFocus = true; + + if (pImpl->mMode == MODE_RELATIVE) + { + pImpl->mState.x = pImpl->mState.y = 0; + + ShowCursor(FALSE); + + pImpl->ClipToWindow(); + } + } + else + { + const int scrollWheel = pImpl->mState.scrollWheelValue; + memset(&pImpl->mState, 0, sizeof(State)); + pImpl->mState.scrollWheelValue = scrollWheel; + + if (pImpl->mMode == MODE_RELATIVE) + { + ClipCursor(nullptr); + } + + pImpl->mInFocus = false; + } + return; + + case WM_INPUT: + if (pImpl->mInFocus && pImpl->mMode == MODE_RELATIVE) + { + RAWINPUT raw; + UINT rawSize = sizeof(raw); + + const UINT resultData = GetRawInputData(reinterpret_cast(lParam), RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER)); + if (resultData == UINT(-1)) + { + throw std::runtime_error("GetRawInputData"); + } + + if (raw.header.dwType == RIM_TYPEMOUSE) + { + if (!(raw.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE)) + { + pImpl->mState.x = raw.data.mouse.lLastX; + pImpl->mState.y = raw.data.mouse.lLastY; + + ResetEvent(pImpl->mRelativeRead.get()); + } + else if (raw.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) + { + // This is used to make Remote Desktop sessons work + const int width = GetSystemMetrics(SM_CXVIRTUALSCREEN); + const int height = GetSystemMetrics(SM_CYVIRTUALSCREEN); + + auto const x = static_cast((float(raw.data.mouse.lLastX) / 65535.0f) * float(width)); + auto const y = static_cast((float(raw.data.mouse.lLastY) / 65535.0f) * float(height)); + + if (pImpl->mRelativeX == INT32_MAX) + { + pImpl->mState.x = pImpl->mState.y = 0; + } + else + { + pImpl->mState.x = x - pImpl->mRelativeX; + pImpl->mState.y = y - pImpl->mRelativeY; + } + + pImpl->mRelativeX = x; + pImpl->mRelativeY = y; + + ResetEvent(pImpl->mRelativeRead.get()); + } + } + } + return; + + case WM_MOUSEMOVE: + break; + + case WM_LBUTTONDOWN: + pImpl->mState.leftButton = true; + break; + + case WM_LBUTTONUP: + pImpl->mState.leftButton = false; + break; + + case WM_RBUTTONDOWN: + pImpl->mState.rightButton = true; + break; + + case WM_RBUTTONUP: + pImpl->mState.rightButton = false; + break; + + case WM_MBUTTONDOWN: + pImpl->mState.middleButton = true; + break; + + case WM_MBUTTONUP: + pImpl->mState.middleButton = false; + break; + + case WM_MOUSEWHEEL: + pImpl->mState.scrollWheelValue += GET_WHEEL_DELTA_WPARAM(wParam); + return; + + case WM_XBUTTONDOWN: + switch (GET_XBUTTON_WPARAM(wParam)) + { + case XBUTTON1: + pImpl->mState.xButton1 = true; + break; + + case XBUTTON2: + pImpl->mState.xButton2 = true; + break; + } + break; + + case WM_XBUTTONUP: + switch (GET_XBUTTON_WPARAM(wParam)) + { + case XBUTTON1: + pImpl->mState.xButton1 = false; + break; + + case XBUTTON2: + pImpl->mState.xButton2 = false; + break; + } + break; + + case WM_MOUSEHOVER: + break; + + default: + // Not a mouse message, so exit + return; + } + + if (pImpl->mMode == MODE_ABSOLUTE) + { + // All mouse messages provide a new pointer position + const int xPos = static_cast(LOWORD(lParam)); // GET_X_LPARAM(lParam); + const int yPos = static_cast(HIWORD(lParam)); // GET_Y_LPARAM(lParam); + + pImpl->mState.x = pImpl->mLastX = xPos; + pImpl->mState.y = pImpl->mLastY = yPos; + } +} + #endif +#pragma endregion #pragma warning( disable : 4355 ) @@ -1420,9 +1444,7 @@ Mouse& Mouse::operator= (Mouse&& moveFrom) noexcept // Public destructor. -Mouse::~Mouse() -{ -} +Mouse::~Mouse() = default; Mouse::State Mouse::GetState() const @@ -1463,7 +1485,7 @@ void Mouse::SetVisible(bool visible) Mouse& Mouse::Get() { if (!Impl::s_mouse || !Impl::s_mouse->mOwner) - throw std::exception("Mouse is a singleton"); + throw std::logic_error("Mouse singleton not created"); return *Impl::s_mouse->mOwner; } @@ -1474,21 +1496,21 @@ Mouse& Mouse::Get() // ButtonStateTracker //====================================================================================== -#define UPDATE_BUTTON_STATE(field) field = static_cast( ( !!state.field ) | ( ( !!state.field ^ !!lastState.field ) << 1 ) ); +#define UPDATE_BUTTON_STATE(field) field = static_cast( ( !!state.field ) | ( ( !!state.field ^ !!lastState.field ) << 1 ) ) void Mouse::ButtonStateTracker::Update(const Mouse::State& state) noexcept { - UPDATE_BUTTON_STATE(leftButton) + UPDATE_BUTTON_STATE(leftButton); assert((!state.leftButton && !lastState.leftButton) == (leftButton == UP)); assert((state.leftButton && lastState.leftButton) == (leftButton == HELD)); assert((!state.leftButton && lastState.leftButton) == (leftButton == RELEASED)); assert((state.leftButton && !lastState.leftButton) == (leftButton == PRESSED)); - UPDATE_BUTTON_STATE(middleButton) - UPDATE_BUTTON_STATE(rightButton) - UPDATE_BUTTON_STATE(xButton1) - UPDATE_BUTTON_STATE(xButton2) + UPDATE_BUTTON_STATE(middleButton); + UPDATE_BUTTON_STATE(rightButton); + UPDATE_BUTTON_STATE(xButton1); + UPDATE_BUTTON_STATE(xButton2); lastState = state; } diff --git a/Tests/GDK/APIRunner.GDK/MicrosoftGame.Config b/Tests/GDK/APIRunner.GDK/MicrosoftGame.Config index 862f3c8d..26004718 100644 --- a/Tests/GDK/APIRunner.GDK/MicrosoftGame.Config +++ b/Tests/GDK/APIRunner.GDK/MicrosoftGame.Config @@ -1,5 +1,5 @@ - + diff --git a/Tests/GDK/ManualTest.GDK/Assets/LargeLogo.png b/Tests/GDK/ManualTest.GDK/Assets/LargeLogo.png new file mode 100644 index 00000000..1827cdd0 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Assets/LargeLogo.png differ diff --git a/Tests/GDK/ManualTest.GDK/Assets/Logo.png b/Tests/GDK/ManualTest.GDK/Assets/Logo.png new file mode 100644 index 00000000..8ac081b3 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Assets/Logo.png differ diff --git a/Tests/GDK/ManualTest.GDK/Assets/SampleUI.csv b/Tests/GDK/ManualTest.GDK/Assets/SampleUI.csv new file mode 100644 index 00000000..8cae55f2 --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/Assets/SampleUI.csv @@ -0,0 +1,10 @@ +#ITEM,ID,X,Y,DX,DY,PARAMETERS +OVERLAY,2000,0,0,1920,1080 +LEGEND,2001,82,850,525,300,[DPad] or Arrows - Select a scenario|[A] or Enter - Start a scenario|[Menu] or Tab - Sign in|[View] or Esc - Exit +BUTTON,2101,82,300,525,50,AddUser +BUTTON,2102,82,375,525,50,Get Achievement Status ID = 1 +BUTTON,2103,82,450,525,50,Complete Achievement ID = 1 +BUTTON,2104,82,525,525,50,Get Achievement Status ID = 2 +BUTTON,2105,82,600,525,50,Set Achievement ID = 2 -> 25% +BUTTON,2106,82,675,525,50,Set Achievement ID = 2 -> 50% +BUTTON,2107,82,750,525,50,Set Achievement ID = 2 -> 100% \ No newline at end of file diff --git a/Tests/GDK/ManualTest.GDK/Assets/SmallLogo.png b/Tests/GDK/ManualTest.GDK/Assets/SmallLogo.png new file mode 100644 index 00000000..d137e617 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Assets/SmallLogo.png differ diff --git a/Tests/GDK/ManualTest.GDK/Assets/SplashScreen.png b/Tests/GDK/ManualTest.GDK/Assets/SplashScreen.png new file mode 100644 index 00000000..ab39d2a5 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Assets/SplashScreen.png differ diff --git a/Tests/GDK/ManualTest.GDK/Assets/StoreLogo.png b/Tests/GDK/ManualTest.GDK/Assets/StoreLogo.png new file mode 100644 index 00000000..62cd3f2b Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Assets/StoreLogo.png differ diff --git a/Tests/GDK/ManualTest.GDK/DeviceResources.cpp b/Tests/GDK/ManualTest.GDK/DeviceResources.cpp new file mode 100644 index 00000000..2f493c9b --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/DeviceResources.cpp @@ -0,0 +1,809 @@ +// +// DeviceResources.cpp - A wrapper for the Direct3D 12/12.X device and swapchain +// + +#include "pch.h" +#include "DeviceResources.h" + +using namespace DirectX; +using namespace DX; + +using Microsoft::WRL::ComPtr; + +#ifdef _GAMING_DESKTOP +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wcovered-switch-default" +#pragma clang diagnostic ignored "-Wswitch-enum" +#endif + +#pragma warning(disable : 4061) + +namespace +{ + inline DXGI_FORMAT NoSRGB(DXGI_FORMAT fmt) noexcept + { + switch (fmt) + { + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: return DXGI_FORMAT_R8G8B8A8_UNORM; + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: return DXGI_FORMAT_B8G8R8A8_UNORM; + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: return DXGI_FORMAT_B8G8R8X8_UNORM; + default: return fmt; + } + } +} +#endif + +// Constructor for DeviceResources. +DeviceResources::DeviceResources( + DXGI_FORMAT backBufferFormat, + DXGI_FORMAT depthBufferFormat, + UINT backBufferCount) noexcept(false) : + m_backBufferIndex(0), + m_fenceValues{}, +#ifdef _GAMING_XBOX + m_framePipelineToken{}, +#endif + m_rtvDescriptorSize(0), + m_screenViewport{}, + m_scissorRect{}, + m_backBufferFormat(backBufferFormat), + m_depthBufferFormat(depthBufferFormat), + m_backBufferCount(backBufferCount), + m_window(nullptr), + m_d3dFeatureLevel(D3D_FEATURE_LEVEL_11_0), + m_outputSize{ 0, 0, 1, 1 }, + m_deviceNotify(nullptr) +{ + if (backBufferCount < 2 || backBufferCount > MAX_BACK_BUFFER_COUNT) + { + throw std::out_of_range("invalid backBufferCount"); + } +} + +// Destructor for DeviceResources. +DeviceResources::~DeviceResources() +{ + // Ensure that the GPU is no longer referencing resources that are about to be destroyed. + WaitForGpu(); + +#ifdef _GAMING_XBOX + // Ensure we present a blank screen before cleaning up resources. + if (m_commandQueue) + { + (void)m_commandQueue->PresentX(0, nullptr, nullptr); + } +#endif +} + +// Configures the Direct3D device, and stores handles to it and the device context. +void DeviceResources::CreateDeviceResources() +{ +#ifdef _GAMING_XBOX + + // Create the DX12 API device object. + D3D12XBOX_CREATE_DEVICE_PARAMETERS params = {}; + params.Version = D3D12_SDK_VERSION; + +#if defined(_DEBUG) + // Enable the debug layer. + params.ProcessDebugFlags = D3D12_PROCESS_DEBUG_FLAG_DEBUG_LAYER_ENABLED; +#elif defined(PROFILE) + // Enable the instrumented driver. + params.ProcessDebugFlags = D3D12XBOX_PROCESS_DEBUG_FLAG_INSTRUMENTED; +#endif + + params.GraphicsCommandQueueRingSizeBytes = static_cast(D3D12XBOX_DEFAULT_SIZE_BYTES); + params.GraphicsScratchMemorySizeBytes = static_cast(D3D12XBOX_DEFAULT_SIZE_BYTES); + params.ComputeScratchMemorySizeBytes = static_cast(D3D12XBOX_DEFAULT_SIZE_BYTES); + + HRESULT hr = D3D12XboxCreateDevice( + nullptr, + ¶ms, + IID_GRAPHICS_PPV_ARGS(m_d3dDevice.ReleaseAndGetAddressOf())); +#ifdef _DEBUG + if (hr == D3D12_ERROR_DRIVER_VERSION_MISMATCH) + { +#ifdef _GAMING_XBOX_SCARLETT + OutputDebugStringA("ERROR: Running a d3d12_xs.lib (Xbox Series X|S) linked binary on an Xbox One is not supported\n"); +#else + OutputDebugStringA("ERROR: Running a d3d12_x.lib (Xbox One) linked binary on a Xbox Series X|S in 'Scarlett' mode is not supported\n"); +#endif + } +#endif + ThrowIfFailed(hr); + + m_d3dDevice->SetName(L"DeviceResources"); + + m_d3dFeatureLevel = D3D_FEATURE_LEVEL_12_0; + +#else // _GAMING_DESKTOP + + DWORD dxgiFactoryFlags = 0; + +#if defined(_DEBUG) + // Enable the debug layer (requires the Graphics Tools "optional feature"). + // + // NOTE: Enabling the debug layer after device creation will invalidate the active device. + { + ComPtr debugController; + if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(debugController.GetAddressOf())))) + { + debugController->EnableDebugLayer(); + } + else + { + OutputDebugStringA("WARNING: Direct3D Debug Device is not available\n"); + } + + ComPtr dxgiInfoQueue; + if (SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(dxgiInfoQueue.GetAddressOf())))) + { + dxgiFactoryFlags = DXGI_CREATE_FACTORY_DEBUG; + + dxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true); + dxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true); + + DXGI_INFO_QUEUE_MESSAGE_ID hide[] = + { + 80 /* IDXGISwapChain::GetContainingOutput: The swapchain's adapter does not control the output on which the swapchain's window resides. */, + }; + DXGI_INFO_QUEUE_FILTER filter = {}; + filter.DenyList.NumIDs = static_cast(std::size(hide)); + filter.DenyList.pIDList = hide; + dxgiInfoQueue->AddStorageFilterEntries(DXGI_DEBUG_DXGI, &filter); + } + } +#endif + + ThrowIfFailed(CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(m_dxgiFactory.ReleaseAndGetAddressOf()))); + + ComPtr adapter; + GetAdapter(adapter.GetAddressOf()); + + // Create the DX12 API device object. + HRESULT hr = D3D12CreateDevice( + adapter.Get(), + D3D_FEATURE_LEVEL_11_0, + IID_PPV_ARGS(m_d3dDevice.ReleaseAndGetAddressOf()) + ); + ThrowIfFailed(hr); + + m_d3dDevice->SetName(L"DeviceResources"); + +#ifndef NDEBUG + // Configure debug device (if active). + ComPtr d3dInfoQueue; + if (SUCCEEDED(m_d3dDevice.As(&d3dInfoQueue))) + { +#ifdef _DEBUG + d3dInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true); + d3dInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true); +#endif + D3D12_MESSAGE_ID hide[] = + { + D3D12_MESSAGE_ID_MAP_INVALID_NULLRANGE, + D3D12_MESSAGE_ID_UNMAP_INVALID_NULLRANGE, + // Workarounds for debug layer issues on hybrid-graphics systems + D3D12_MESSAGE_ID_EXECUTECOMMANDLISTS_WRONGSWAPCHAINBUFFERREFERENCE, + D3D12_MESSAGE_ID_RESOURCE_BARRIER_MISMATCHING_COMMAND_LIST_TYPE, + }; + D3D12_INFO_QUEUE_FILTER filter = {}; + filter.DenyList.NumIDs = static_cast(std::size(hide)); + filter.DenyList.pIDList = hide; + d3dInfoQueue->AddStorageFilterEntries(&filter); + } +#endif + + // Determine maximum supported feature level for this device + static const D3D_FEATURE_LEVEL s_featureLevels[] = + { +#if defined(NTDDI_WIN10_FE) && (NTDDI_VERSION >= NTDDI_WIN10_FE) + D3D_FEATURE_LEVEL_12_2, +#endif + D3D_FEATURE_LEVEL_12_1, + D3D_FEATURE_LEVEL_12_0, + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + }; + + D3D12_FEATURE_DATA_FEATURE_LEVELS featLevels = + { + static_cast(std::size(s_featureLevels)), s_featureLevels, D3D_FEATURE_LEVEL_11_0 + }; + + hr = m_d3dDevice->CheckFeatureSupport(D3D12_FEATURE_FEATURE_LEVELS, &featLevels, sizeof(featLevels)); + if (SUCCEEDED(hr)) + { + m_d3dFeatureLevel = featLevels.MaxSupportedFeatureLevel; + } + else + { + m_d3dFeatureLevel = D3D_FEATURE_LEVEL_11_0; + } + +#endif + + // Create the command queue. + D3D12_COMMAND_QUEUE_DESC queueDesc = {}; + queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + + ThrowIfFailed(m_d3dDevice->CreateCommandQueue(&queueDesc, IID_GRAPHICS_PPV_ARGS(m_commandQueue.ReleaseAndGetAddressOf()))); + + m_commandQueue->SetName(L"DeviceResources"); + + // Create descriptor heaps for render target views and depth stencil views. + D3D12_DESCRIPTOR_HEAP_DESC rtvDescriptorHeapDesc = {}; + rtvDescriptorHeapDesc.NumDescriptors = m_backBufferCount; + rtvDescriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + + ThrowIfFailed(m_d3dDevice->CreateDescriptorHeap(&rtvDescriptorHeapDesc, IID_GRAPHICS_PPV_ARGS(m_rtvDescriptorHeap.ReleaseAndGetAddressOf()))); + + m_rtvDescriptorHeap->SetName(L"DeviceResources"); + + m_rtvDescriptorSize = m_d3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + + if (m_depthBufferFormat != DXGI_FORMAT_UNKNOWN) + { + D3D12_DESCRIPTOR_HEAP_DESC dsvDescriptorHeapDesc = {}; + dsvDescriptorHeapDesc.NumDescriptors = 1; + dsvDescriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV; + + ThrowIfFailed(m_d3dDevice->CreateDescriptorHeap(&dsvDescriptorHeapDesc, IID_GRAPHICS_PPV_ARGS(m_dsvDescriptorHeap.ReleaseAndGetAddressOf()))); + + m_dsvDescriptorHeap->SetName(L"DeviceResources"); + } + + // Create a command allocator for each back buffer that will be rendered to. + for (UINT n = 0; n < m_backBufferCount; n++) + { + ThrowIfFailed(m_d3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_GRAPHICS_PPV_ARGS(m_commandAllocators[n].ReleaseAndGetAddressOf()))); + + wchar_t name[25] = {}; + swprintf_s(name, L"Render target %u", n); + m_commandAllocators[n]->SetName(name); + } + + // Create a command list for recording graphics commands. + ThrowIfFailed(m_d3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocators[0].Get(), nullptr, IID_GRAPHICS_PPV_ARGS(m_commandList.ReleaseAndGetAddressOf()))); + ThrowIfFailed(m_commandList->Close()); + + m_commandList->SetName(L"DeviceResources"); + + // Create a fence for tracking GPU execution progress. + ThrowIfFailed(m_d3dDevice->CreateFence(m_fenceValues[m_backBufferIndex], D3D12_FENCE_FLAG_NONE, IID_GRAPHICS_PPV_ARGS(m_fence.ReleaseAndGetAddressOf()))); + m_fenceValues[m_backBufferIndex]++; + + m_fence->SetName(L"DeviceResources"); + + m_fenceEvent.Attach(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE)); + if (!m_fenceEvent.IsValid()) + { + throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "CreateEventEx"); + } + +#ifdef _GAMING_XBOX + RegisterFrameEvents(); +#endif +} + +// These resources need to be recreated every time the window size is changed. +void DeviceResources::CreateWindowSizeDependentResources() +{ + if (!m_window) + { + throw std::logic_error("Call SetWindow with a valid Win32 window handle"); + } + + // Wait until all previous GPU work is complete. + WaitForGpu(); + +#ifdef _GAMING_XBOX + // Ensure we present a blank screen before cleaning up resources. + ThrowIfFailed(m_commandQueue->PresentX(0, nullptr, nullptr)); +#endif + + // Release resources that are tied to the swap chain and update fence values. + for (UINT n = 0; n < m_backBufferCount; n++) + { + m_renderTargets[n].Reset(); + m_fenceValues[n] = m_fenceValues[m_backBufferIndex]; + } + + // Determine the render target size in pixels. + const UINT backBufferWidth = std::max(static_cast(m_outputSize.right - m_outputSize.left), 1u); + const UINT backBufferHeight = std::max(static_cast(m_outputSize.bottom - m_outputSize.top), 1u); + +#ifdef _GAMING_XBOX + + // Obtain the back buffers for this window which will be the final render targets + // and create render target views for each of them. + CD3DX12_HEAP_PROPERTIES swapChainHeapProperties(D3D12_HEAP_TYPE_DEFAULT); + + D3D12_RESOURCE_DESC swapChainBufferDesc = CD3DX12_RESOURCE_DESC::Tex2D( + m_backBufferFormat, + backBufferWidth, + backBufferHeight, + 1, // This resource has only one texture. + 1 // Use a single mipmap level. + ); + swapChainBufferDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + + D3D12_CLEAR_VALUE swapChainOptimizedClearValue = {}; + swapChainOptimizedClearValue.Format = m_backBufferFormat; + + for (UINT n = 0; n < m_backBufferCount; n++) + { + ThrowIfFailed(m_d3dDevice->CreateCommittedResource( + &swapChainHeapProperties, + D3D12_HEAP_FLAG_ALLOW_DISPLAY, + &swapChainBufferDesc, + D3D12_RESOURCE_STATE_PRESENT, + &swapChainOptimizedClearValue, + IID_GRAPHICS_PPV_ARGS(m_renderTargets[n].GetAddressOf()))); + + wchar_t name[25] = {}; + swprintf_s(name, L"Render target %u", n); + m_renderTargets[n]->SetName(name); + + D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {}; + rtvDesc.Format = m_backBufferFormat; + rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + + CD3DX12_CPU_DESCRIPTOR_HANDLE rtvDescriptor( + m_rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), + static_cast(n), m_rtvDescriptorSize); + m_d3dDevice->CreateRenderTargetView(m_renderTargets[n].Get(), &rtvDesc, rtvDescriptor); + } + + // Reset the index to the current back buffer. + m_backBufferIndex = 0; + +#else // _GAMING_DESKTOP + + DXGI_FORMAT backBufferFormat = NoSRGB(m_backBufferFormat); + + // If the swap chain already exists, resize it, otherwise create one. + if (m_swapChain) + { + // If the swap chain already exists, resize it. + HRESULT hr = m_swapChain->ResizeBuffers( + m_backBufferCount, + backBufferWidth, + backBufferHeight, + backBufferFormat, + 0 + ); + + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) + { +#ifdef _DEBUG + char buff[64] = {}; + sprintf_s(buff, "Device Lost on ResizeBuffers: Reason code 0x%08X\n", + static_cast((hr == DXGI_ERROR_DEVICE_REMOVED) ? m_d3dDevice->GetDeviceRemovedReason() : hr)); + OutputDebugStringA(buff); +#endif + // If the device was removed for any reason, a new device and swap chain will need to be created. + HandleDeviceLost(); + + // Everything is set up now. Do not continue execution of this method. HandleDeviceLost will reenter this method + // and correctly set up the new device. + return; + } + else + { + ThrowIfFailed(hr); + } + } + else + { + // Create a descriptor for the swap chain. + DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; + swapChainDesc.Width = backBufferWidth; + swapChainDesc.Height = backBufferHeight; + swapChainDesc.Format = backBufferFormat; + swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapChainDesc.BufferCount = m_backBufferCount; + swapChainDesc.SampleDesc.Count = 1; + swapChainDesc.SampleDesc.Quality = 0; + swapChainDesc.Scaling = DXGI_SCALING_STRETCH; + swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + + DXGI_SWAP_CHAIN_FULLSCREEN_DESC fsSwapChainDesc = {}; + fsSwapChainDesc.Windowed = TRUE; + + // Create a swap chain for the window. + ComPtr swapChain; + ThrowIfFailed(m_dxgiFactory->CreateSwapChainForHwnd( + m_commandQueue.Get(), + m_window, + &swapChainDesc, + &fsSwapChainDesc, + nullptr, + swapChain.GetAddressOf() + )); + + ThrowIfFailed(swapChain.As(&m_swapChain)); + + // This class does not support exclusive full-screen mode and prevents DXGI from responding to the ALT+ENTER shortcut + ThrowIfFailed(m_dxgiFactory->MakeWindowAssociation(m_window, DXGI_MWA_NO_ALT_ENTER)); + } + + // Obtain the back buffers for this window which will be the final render targets + // and create render target views for each of them. + for (UINT n = 0; n < m_backBufferCount; n++) + { + ThrowIfFailed(m_swapChain->GetBuffer(n, IID_PPV_ARGS(m_renderTargets[n].GetAddressOf()))); + + wchar_t name[25] = {}; + swprintf_s(name, L"Render target %u", n); + m_renderTargets[n]->SetName(name); + + D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {}; + rtvDesc.Format = m_backBufferFormat; + rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + + CD3DX12_CPU_DESCRIPTOR_HANDLE rtvDescriptor( + m_rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), + static_cast(n), m_rtvDescriptorSize); + m_d3dDevice->CreateRenderTargetView(m_renderTargets[n].Get(), &rtvDesc, rtvDescriptor); + } + + // Reset the index to the current back buffer. + m_backBufferIndex = m_swapChain->GetCurrentBackBufferIndex(); + +#endif + + if (m_depthBufferFormat != DXGI_FORMAT_UNKNOWN) + { + // Allocate a 2-D surface as the depth/stencil buffer and create a depth/stencil view + // on this surface. + CD3DX12_HEAP_PROPERTIES depthHeapProperties(D3D12_HEAP_TYPE_DEFAULT); + + D3D12_RESOURCE_DESC depthStencilDesc = CD3DX12_RESOURCE_DESC::Tex2D( + m_depthBufferFormat, + backBufferWidth, + backBufferHeight, + 1, // This depth stencil view has only one texture. + 1 // Use a single mipmap level. + ); + depthStencilDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + + D3D12_CLEAR_VALUE depthOptimizedClearValue = {}; + depthOptimizedClearValue.Format = m_depthBufferFormat; + depthOptimizedClearValue.DepthStencil.Depth = 1.0f; + depthOptimizedClearValue.DepthStencil.Stencil = 0; + + ThrowIfFailed(m_d3dDevice->CreateCommittedResource( + &depthHeapProperties, + D3D12_HEAP_FLAG_NONE, + &depthStencilDesc, + D3D12_RESOURCE_STATE_DEPTH_WRITE, + &depthOptimizedClearValue, + IID_GRAPHICS_PPV_ARGS(m_depthStencil.ReleaseAndGetAddressOf()) + )); + + m_depthStencil->SetName(L"Depth stencil"); + + D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {}; + dsvDesc.Format = m_depthBufferFormat; + dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; + + m_d3dDevice->CreateDepthStencilView(m_depthStencil.Get(), &dsvDesc, m_dsvDescriptorHeap->GetCPUDescriptorHandleForHeapStart()); + } + + // Set the 3D rendering viewport and scissor rectangle to target the entire window. + m_screenViewport.TopLeftX = m_screenViewport.TopLeftY = 0.f; + m_screenViewport.Width = static_cast(backBufferWidth); + m_screenViewport.Height = static_cast(backBufferHeight); + m_screenViewport.MinDepth = D3D12_MIN_DEPTH; + m_screenViewport.MaxDepth = D3D12_MAX_DEPTH; + + m_scissorRect.left = m_scissorRect.top = 0; + m_scissorRect.right = static_cast(backBufferWidth); + m_scissorRect.bottom = static_cast(backBufferHeight); +} + +// This method is called when the Win32 window is created (or re-created). +void DeviceResources::SetWindow(HWND window, int width, int height) noexcept +{ + m_window = window; + + m_outputSize.left = m_outputSize.top = 0; + m_outputSize.right = width; + m_outputSize.bottom = height; +} + +// This method is called when the Win32 window changes size. +bool DeviceResources::WindowSizeChanged(int width, int height) +{ + RECT newRc; + newRc.left = newRc.top = 0; + newRc.right = width; + newRc.bottom = height; + if (newRc.left == m_outputSize.left + && newRc.top == m_outputSize.top + && newRc.right == m_outputSize.right + && newRc.bottom == m_outputSize.bottom) + { + return false; + } + + m_outputSize = newRc; + CreateWindowSizeDependentResources(); + return true; +} + +// Recreate all device resources and set them back to the current state. +void DeviceResources::HandleDeviceLost() +{ +#ifdef _GAMING_DESKTOP + if (m_deviceNotify) + { + m_deviceNotify->OnDeviceLost(); + } + + for (UINT n = 0; n < m_backBufferCount; n++) + { + m_commandAllocators[n].Reset(); + m_renderTargets[n].Reset(); + } + + m_depthStencil.Reset(); + m_commandQueue.Reset(); + m_commandList.Reset(); + m_fence.Reset(); + m_rtvDescriptorHeap.Reset(); + m_dsvDescriptorHeap.Reset(); + m_swapChain.Reset(); + m_d3dDevice.Reset(); + m_dxgiFactory.Reset(); + +#ifdef _DEBUG + { + ComPtr dxgiDebug; + if (SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(&dxgiDebug)))) + { + dxgiDebug->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_FLAGS(DXGI_DEBUG_RLO_SUMMARY | DXGI_DEBUG_RLO_IGNORE_INTERNAL)); + } + } +#endif + + CreateDeviceResources(); + CreateWindowSizeDependentResources(); + + if (m_deviceNotify) + { + m_deviceNotify->OnDeviceRestored(); + } +#endif +} + +// Prepare the command list and render target for rendering. +void DeviceResources::Prepare(D3D12_RESOURCE_STATES beforeState, D3D12_RESOURCE_STATES afterState) +{ +#ifdef _GAMING_XBOX + // Wait until frame start is signaled + m_framePipelineToken = D3D12XBOX_FRAME_PIPELINE_TOKEN_NULL; + ThrowIfFailed(m_d3dDevice->WaitFrameEventX(D3D12XBOX_FRAME_EVENT_ORIGIN, INFINITE, nullptr, D3D12XBOX_WAIT_FRAME_EVENT_FLAG_NONE, &m_framePipelineToken)); +#endif + + // Reset command list and allocator. + ThrowIfFailed(m_commandAllocators[m_backBufferIndex]->Reset()); + ThrowIfFailed(m_commandList->Reset(m_commandAllocators[m_backBufferIndex].Get(), nullptr)); + + if (beforeState != afterState) + { + // Transition the render target into the correct state to allow for drawing into it. + D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_backBufferIndex].Get(), + beforeState, afterState); + m_commandList->ResourceBarrier(1, &barrier); + } +} + +// Present the contents of the swap chain to the screen. +void DeviceResources::Present(D3D12_RESOURCE_STATES beforeState) +{ + if (beforeState != D3D12_RESOURCE_STATE_PRESENT) + { + // Transition the render target to the state that allows it to be presented to the display. + D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_backBufferIndex].Get(), beforeState, D3D12_RESOURCE_STATE_PRESENT); + m_commandList->ResourceBarrier(1, &barrier); + } + + // Send the command list off to the GPU for processing. + ThrowIfFailed(m_commandList->Close()); + m_commandQueue->ExecuteCommandLists(1, CommandListCast(m_commandList.GetAddressOf())); + +#ifdef _GAMING_XBOX + + // Present the backbuffer using the PresentX API. + D3D12XBOX_PRESENT_PLANE_PARAMETERS planeParameters = {}; + planeParameters.Token = m_framePipelineToken; + planeParameters.ResourceCount = 1; + planeParameters.ppResources = m_renderTargets[m_backBufferIndex].GetAddressOf(); + + ThrowIfFailed( + m_commandQueue->PresentX(1, &planeParameters, nullptr) + ); + + // Xbox One apps do not need to handle DXGI_ERROR_DEVICE_REMOVED or DXGI_ERROR_DEVICE_RESET. + +#else + + // The first argument instructs DXGI to block until VSync, putting the application + // to sleep until the next VSync. This ensures we don't waste any cycles rendering + // frames that will never be displayed to the screen. + HRESULT hr = m_swapChain->Present(1, 0); + + // If the device was reset we must completely reinitialize the renderer. + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) + { +#ifdef _DEBUG + char buff[64] = {}; + sprintf_s(buff, "Device Lost on Present: Reason code 0x%08X\n", + static_cast((hr == DXGI_ERROR_DEVICE_REMOVED) ? m_d3dDevice->GetDeviceRemovedReason() : hr)); + OutputDebugStringA(buff); +#endif + HandleDeviceLost(); + } + else + { + ThrowIfFailed(hr); + } + +#endif + + MoveToNextFrame(); +} + +// Handle GPU suspend/resume +void DeviceResources::Suspend() +{ +#ifdef _GAMING_XBOX + m_commandQueue->SuspendX(0); +#endif +} + +void DeviceResources::Resume() +{ +#ifdef _GAMING_XBOX + m_commandQueue->ResumeX(); + + RegisterFrameEvents(); +#endif +} + +// Wait for pending GPU work to complete. +void DeviceResources::WaitForGpu() noexcept +{ + if (m_commandQueue && m_fence && m_fenceEvent.IsValid()) + { + // Schedule a Signal command in the GPU queue. + UINT64 fenceValue = m_fenceValues[m_backBufferIndex]; + if (SUCCEEDED(m_commandQueue->Signal(m_fence.Get(), fenceValue))) + { + // Wait until the Signal has been processed. + if (SUCCEEDED(m_fence->SetEventOnCompletion(fenceValue, m_fenceEvent.Get()))) + { + WaitForSingleObjectEx(m_fenceEvent.Get(), INFINITE, FALSE); + + // Increment the fence value for the current frame. + m_fenceValues[m_backBufferIndex]++; + } + } + } +} + +// Prepare to render the next frame. +void DeviceResources::MoveToNextFrame() +{ + // Schedule a Signal command in the queue. + const UINT64 currentFenceValue = m_fenceValues[m_backBufferIndex]; + ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), currentFenceValue)); + + // Update the back buffer index. +#ifdef _GAMING_XBOX + m_backBufferIndex = (m_backBufferIndex + 1) % m_backBufferCount; +#else + m_backBufferIndex = m_swapChain->GetCurrentBackBufferIndex(); +#endif + + // If the next frame is not ready to be rendered yet, wait until it is ready. + if (m_fence->GetCompletedValue() < m_fenceValues[m_backBufferIndex]) + { + ThrowIfFailed(m_fence->SetEventOnCompletion(m_fenceValues[m_backBufferIndex], m_fenceEvent.Get())); + WaitForSingleObjectEx(m_fenceEvent.Get(), INFINITE, FALSE); + } + + // Set the fence value for the next frame. + m_fenceValues[m_backBufferIndex] = currentFenceValue + 1; +} + +#ifdef _GAMING_XBOX +// Set frame interval and register for frame events +void DeviceResources::RegisterFrameEvents() +{ + // First, retrieve the underlying DXGI device from the D3D device. + ComPtr dxgiDevice; + ThrowIfFailed(m_d3dDevice.As(&dxgiDevice)); + + // Identify the physical adapter (GPU or card) this device is running on. + ComPtr dxgiAdapter; + ThrowIfFailed(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf())); + + // Retrieve the outputs for the adapter. + ComPtr dxgiOutput; + ThrowIfFailed(dxgiAdapter->EnumOutputs(0, dxgiOutput.GetAddressOf())); + + // Set frame interval and register for frame events + ThrowIfFailed(m_d3dDevice->SetFrameIntervalX( + dxgiOutput.Get(), + D3D12XBOX_FRAME_INTERVAL_60_HZ, + m_backBufferCount - 1u /* Allow n-1 frames of latency */, + D3D12XBOX_FRAME_INTERVAL_FLAG_NONE)); + + ThrowIfFailed(m_d3dDevice->ScheduleFrameEventX( + D3D12XBOX_FRAME_EVENT_ORIGIN, + 0U, + nullptr, + D3D12XBOX_SCHEDULE_FRAME_EVENT_FLAG_NONE)); +} +#else +// This method acquires the first available hardware adapter that supports Direct3D 12. +// If no such adapter can be found, try WARP. Otherwise throw an exception. +void DeviceResources::GetAdapter(IDXGIAdapter1** ppAdapter) +{ + *ppAdapter = nullptr; + + ComPtr adapter; + for (UINT adapterIndex = 0; + SUCCEEDED(m_dxgiFactory->EnumAdapterByGpuPreference( + adapterIndex, + DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, + IID_PPV_ARGS(adapter.ReleaseAndGetAddressOf()))); + adapterIndex++) + { + DXGI_ADAPTER_DESC1 desc; + ThrowIfFailed(adapter->GetDesc1(&desc)); + + if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) + { + // Don't select the Basic Render Driver adapter. + continue; + } + + // Check to see if the adapter supports Direct3D 12, but don't create the actual device yet. + if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr))) + { +#ifdef _DEBUG + wchar_t buff[256] = {}; + swprintf_s(buff, L"Direct3D Adapter (%u): VID:%04X, PID:%04X - %ls\n", adapterIndex, desc.VendorId, desc.DeviceId, desc.Description); + OutputDebugStringW(buff); +#endif + break; + } + } + +#if !defined(NDEBUG) + if (!adapter) + { + // Try WARP12 instead + if (FAILED(m_dxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(adapter.ReleaseAndGetAddressOf())))) + { + throw std::runtime_error("WARP12 not available. Enable the 'Graphics Tools' optional feature"); + } + + OutputDebugStringA("Direct3D Adapter - WARP12\n"); + } +#endif + + if (!adapter) + { + throw std::runtime_error("No Direct3D 12 device found"); + } + + *ppAdapter = adapter.Detach(); +} +#endif diff --git a/Tests/GDK/ManualTest.GDK/DeviceResources.h b/Tests/GDK/ManualTest.GDK/DeviceResources.h new file mode 100644 index 00000000..8937789e --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/DeviceResources.h @@ -0,0 +1,135 @@ +// +// DeviceResources.h - A wrapper for the Direct3D 12/12.X device and swapchain +// + +#pragma once + +namespace DX +{ + // Provides an interface for an application that owns DeviceResources to be notified of the device being lost or created. + interface IDeviceNotify + { + virtual void OnDeviceLost() = 0; + virtual void OnDeviceRestored() = 0; + + protected: + ~IDeviceNotify() = default; + }; + + // Controls all the DirectX device resources. + class DeviceResources + { + public: + DeviceResources(DXGI_FORMAT backBufferFormat = DXGI_FORMAT_B8G8R8A8_UNORM, + DXGI_FORMAT depthBufferFormat = DXGI_FORMAT_D32_FLOAT, + UINT backBufferCount = 2) noexcept(false); + ~DeviceResources(); + + DeviceResources(DeviceResources&&) = default; + DeviceResources& operator= (DeviceResources&&) = default; + + DeviceResources(DeviceResources const&) = delete; + DeviceResources& operator= (DeviceResources const&) = delete; + + void CreateDeviceResources(); + void CreateWindowSizeDependentResources(); + void SetWindow(HWND window, int width, int height) noexcept; + bool WindowSizeChanged(int width, int height); + void HandleDeviceLost(); + void RegisterDeviceNotify(IDeviceNotify* deviceNotify) noexcept { m_deviceNotify = deviceNotify; } + void Prepare(D3D12_RESOURCE_STATES beforeState = D3D12_RESOURCE_STATE_PRESENT, + D3D12_RESOURCE_STATES afterState = D3D12_RESOURCE_STATE_RENDER_TARGET); + void Present(D3D12_RESOURCE_STATES beforeState = D3D12_RESOURCE_STATE_RENDER_TARGET); + void Suspend(); + void Resume(); + void WaitForGpu() noexcept; + + // Device Accessors. + RECT GetOutputSize() const noexcept { return m_outputSize; } + + // Direct3D Accessors. + auto GetD3DDevice() const noexcept { return m_d3dDevice.Get(); } +#ifdef _GAMING_DESKTOP + auto GetSwapChain() const noexcept { return m_swapChain.Get(); } + auto GetDXGIFactory() const noexcept { return m_dxgiFactory.Get(); } +#endif + HWND GetWindow() const noexcept { return m_window; } + D3D_FEATURE_LEVEL GetDeviceFeatureLevel() const noexcept { return m_d3dFeatureLevel; } + ID3D12Resource* GetRenderTarget() const noexcept { return m_renderTargets[m_backBufferIndex].Get(); } + ID3D12Resource* GetDepthStencil() const noexcept { return m_depthStencil.Get(); } + ID3D12CommandQueue* GetCommandQueue() const noexcept { return m_commandQueue.Get(); } + ID3D12CommandAllocator* GetCommandAllocator() const noexcept { return m_commandAllocators[m_backBufferIndex].Get(); } + auto GetCommandList() const noexcept { return m_commandList.Get(); } + DXGI_FORMAT GetBackBufferFormat() const noexcept { return m_backBufferFormat; } + DXGI_FORMAT GetDepthBufferFormat() const noexcept { return m_depthBufferFormat; } + D3D12_VIEWPORT GetScreenViewport() const noexcept { return m_screenViewport; } + D3D12_RECT GetScissorRect() const noexcept { return m_scissorRect; } + UINT GetCurrentFrameIndex() const noexcept { return m_backBufferIndex; } + UINT GetBackBufferCount() const noexcept { return m_backBufferCount; } + + CD3DX12_CPU_DESCRIPTOR_HANDLE GetRenderTargetView() const noexcept + { + return CD3DX12_CPU_DESCRIPTOR_HANDLE( + m_rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), + static_cast(m_backBufferIndex), m_rtvDescriptorSize); + } + CD3DX12_CPU_DESCRIPTOR_HANDLE GetDepthStencilView() const noexcept + { + return CD3DX12_CPU_DESCRIPTOR_HANDLE(m_dsvDescriptorHeap->GetCPUDescriptorHandleForHeapStart()); + } + + private: + void MoveToNextFrame(); +#ifdef _GAMING_XBOX + void RegisterFrameEvents(); +#else + void GetAdapter(IDXGIAdapter1** ppAdapter); +#endif + + static constexpr size_t MAX_BACK_BUFFER_COUNT = 3; + + UINT m_backBufferIndex; + + // Direct3D objects. + Microsoft::WRL::ComPtr m_d3dDevice; + Microsoft::WRL::ComPtr m_commandList; + Microsoft::WRL::ComPtr m_commandQueue; + Microsoft::WRL::ComPtr m_commandAllocators[MAX_BACK_BUFFER_COUNT]; + + // Swap chain objects. +#ifdef _GAMING_DESKTOP + Microsoft::WRL::ComPtr m_dxgiFactory; + Microsoft::WRL::ComPtr m_swapChain; +#endif + Microsoft::WRL::ComPtr m_renderTargets[MAX_BACK_BUFFER_COUNT]; + Microsoft::WRL::ComPtr m_depthStencil; + + // Presentation fence objects. + Microsoft::WRL::ComPtr m_fence; + UINT64 m_fenceValues[MAX_BACK_BUFFER_COUNT]; + Microsoft::WRL::Wrappers::Event m_fenceEvent; +#ifdef _GAMING_XBOX + D3D12XBOX_FRAME_PIPELINE_TOKEN m_framePipelineToken; +#endif + + // Direct3D rendering objects. + Microsoft::WRL::ComPtr m_rtvDescriptorHeap; + Microsoft::WRL::ComPtr m_dsvDescriptorHeap; + UINT m_rtvDescriptorSize; + D3D12_VIEWPORT m_screenViewport; + D3D12_RECT m_scissorRect; + + // Direct3D properties. + DXGI_FORMAT m_backBufferFormat; + DXGI_FORMAT m_depthBufferFormat; + UINT m_backBufferCount; + + // Cached device properties. + HWND m_window; + D3D_FEATURE_LEVEL m_d3dFeatureLevel; + RECT m_outputSize; + + // The IDeviceNotify can be held directly as it owns the DeviceResources. + IDeviceNotify* m_deviceNotify; + }; +} diff --git a/Tests/GDK/ManualTest.GDK/Main.cpp b/Tests/GDK/ManualTest.GDK/Main.cpp new file mode 100644 index 00000000..4a6362be --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/Main.cpp @@ -0,0 +1,423 @@ +//-------------------------------------------------------------------------------------- +// Main.cpp +// +// Entry point for Microsoft Game Development Kit (GDK) +// +// Advanced Technology Group (ATG) +// Copyright (C) Microsoft Corporation. All rights reserved. +//-------------------------------------------------------------------------------------- + +#include "pch.h" +#include "ManualTest.h" + +#include +#include +#include + +#ifdef ATG_ENABLE_TELEMETRY +#include "ATGTelemetry.h" +#endif + +using namespace DirectX; + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wcovered-switch-default" +#pragma clang diagnostic ignored "-Wswitch-enum" +#endif + +#pragma warning(disable : 4061) + +namespace +{ + std::unique_ptr g_sample; +#ifdef _GAMING_XBOX + HANDLE g_plmSuspendComplete = nullptr; + HANDLE g_plmSignalResume = nullptr; +#endif +} + +LPCWSTR g_szAppName = L"ManualTest"; + +LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); +void ExitSample() noexcept; + +// Entry point +int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) +{ + UNREFERENCED_PARAMETER(lpCmdLine); + + if (!XMVerifyCPUSupport()) + { +#ifdef _DEBUG + OutputDebugStringA("ERROR: This hardware does not support the required instruction set.\n"); +#if defined(_GAMING_XBOX) && defined(__AVX2__) + OutputDebugStringA("This may indicate a Gaming.Xbox.Scarlett.x64 binary is being run on an Xbox One.\n"); +#endif +#endif + return 1; + } + + // Initialize COM for WIC usage + if (FAILED(CoInitializeEx(nullptr, COINITBASE_MULTITHREADED))) + return 1; + +#ifdef _GAMING_DESKTOP + // NOTE: When running the app from the Start Menu (required for + // Store API's to work) the Current Working Directory will be + // returned as C:\Windows\system32 unless you overwrite it. + // The sample relies on the font and image files in the .exe's + // directory and so we do the following to set the working + // directory to what we want. + char dir[_MAX_PATH] = {}; + if (GetModuleFileNameA(nullptr, dir, _MAX_PATH) > 0) + { + std::string exe = dir; + exe = exe.substr(0, exe.find_last_of("\\")); + std::ignore = SetCurrentDirectoryA(exe.c_str()); + } +#endif + + HRESULT hr = XGameRuntimeInitialize(); + if (FAILED(hr)) + { + if (hr == E_GAMERUNTIME_DLL_NOT_FOUND || hr == E_GAMERUNTIME_VERSION_MISMATCH) + { +#ifdef _GAMING_DESKTOP + std::ignore = MessageBoxW(nullptr, L"Game Runtime is not installed on this system or needs updating.", g_szAppName, MB_ICONERROR | MB_OK); +#endif + } + return 1; + } + +#ifdef _GAMING_XBOX + // Default main thread to CPU 0 + SetThreadAffinityMask(GetCurrentThread(), 0x1); +#endif + + g_sample = std::make_unique(); + + // Register class and create window +#ifdef _GAMING_XBOX + PAPPSTATE_REGISTRATION hPLM = {}; +#endif + { + // Register class + WNDCLASSEXW wcex = {}; + wcex.cbSize = sizeof(WNDCLASSEXW); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.hInstance = hInstance; + wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW); + wcex.hbrBackground = reinterpret_cast(COLOR_WINDOW + 1); + wcex.lpszClassName = L"ManualTestWindowClass"; + if (!RegisterClassExW(&wcex)) + return 1; + + // Create window +#ifdef _GAMING_XBOX + RECT rc = { 0, 0, 1920, 1080 }; +#else + int w, h; + g_sample->GetDefaultSize(w, h); + + RECT rc = { 0, 0, static_cast(w), static_cast(h) }; + AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE); +#endif + + HWND hwnd = CreateWindowExW(0, L"ManualTestWindowClass", g_szAppName, WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, nullptr, nullptr, hInstance, + nullptr); + if (!hwnd) + return 1; + + ShowWindow(hwnd, nCmdShow); + + SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(g_sample.get())); + + // Sample Usage Telemetry + // + // Disable or remove this code block to opt-out of sample usage telemetry + + GetClientRect(hwnd, &rc); + + g_sample->Initialize(hwnd, rc.right - rc.left, rc.bottom - rc.top); + +#ifdef _GAMING_XBOX + g_plmSuspendComplete = CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE); + g_plmSignalResume = CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE); + if (!g_plmSuspendComplete || !g_plmSignalResume) + return 1; + + if (RegisterAppStateChangeNotification([](BOOLEAN quiesced, PVOID context) + { + if (quiesced) + { + ResetEvent(g_plmSuspendComplete); + ResetEvent(g_plmSignalResume); + + // To ensure we use the main UI thread to process the notification, we self-post a message + PostMessage(reinterpret_cast(context), WM_USER, 0, 0); + + // To defer suspend, you must wait to exit this callback + std::ignore = WaitForSingleObject(g_plmSuspendComplete, INFINITE); + } + else + { + SetEvent(g_plmSignalResume); + } + }, hwnd, &hPLM)) + return 1; +#endif + } + + // Main message loop + MSG msg = {}; + while (WM_QUIT != msg.message) + { + if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + else + { + g_sample->Tick(); + } + } + + g_sample.reset(); + +#ifdef _GAMING_XBOX + UnregisterAppStateChangeNotification(hPLM); + + CloseHandle(g_plmSuspendComplete); + CloseHandle(g_plmSignalResume); +#endif + + + XGameRuntimeUninitialize(); + + CoUninitialize(); + + return static_cast(msg.wParam); +} + +// Windows procedure +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ +#ifdef _GAMING_DESKTOP + static bool s_in_sizemove = false; + static bool s_in_suspend = false; + static bool s_minimized = false; + static bool s_fullscreen = false; +#endif + + auto sample = reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_USERDATA)); + + switch (message) + { + case WM_ACTIVATEAPP: + if (sample) + { + Keyboard::ProcessMessage(message, wParam, lParam); + Mouse::ProcessMessage(message, wParam, lParam); + + if (wParam) + { + sample->OnActivated(); + } + else + { + sample->OnDeactivated(); + } + } + break; + + case WM_ACTIVATE: + Keyboard::ProcessMessage(message, wParam, lParam); + Mouse::ProcessMessage(message, wParam, lParam); + break; + + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MOUSEWHEEL: + case WM_XBUTTONDOWN: + case WM_XBUTTONUP: + Mouse::ProcessMessage(message, wParam, lParam); + break; + +#ifdef _GAMING_XBOX + case WM_USER: + if (sample) + { + sample->OnSuspending(); + + // Complete deferral + SetEvent(g_plmSuspendComplete); + + std::ignore = WaitForSingleObject(g_plmSignalResume, INFINITE); + + sample->OnResuming(); + } + break; +#else + case WM_PAINT: + if (s_in_sizemove && sample) + { + sample->Tick(); + } + else + { + PAINTSTRUCT ps; + std::ignore = BeginPaint(hWnd, &ps); + EndPaint(hWnd, &ps); + } + break; + + case WM_MOVE: + if (sample) + { + sample->OnWindowMoved(); + } + break; + + case WM_SIZE: + if (wParam == SIZE_MINIMIZED) + { + if (!s_minimized) + { + s_minimized = true; + if (!s_in_suspend && sample) + sample->OnSuspending(); + s_in_suspend = true; + } + } + else if (s_minimized) + { + s_minimized = false; + if (s_in_suspend && sample) + sample->OnResuming(); + s_in_suspend = false; + } + else if (!s_in_sizemove && sample) + { + sample->OnWindowSizeChanged(LOWORD(lParam), HIWORD(lParam)); + } + break; + + case WM_ENTERSIZEMOVE: + s_in_sizemove = true; + break; + + case WM_EXITSIZEMOVE: + s_in_sizemove = false; + if (sample) + { + RECT rc; + GetClientRect(hWnd, &rc); + + sample->OnWindowSizeChanged(rc.right - rc.left, rc.bottom - rc.top); + } + break; + + case WM_GETMINMAXINFO: + if (lParam) + { + auto info = reinterpret_cast(lParam); + info->ptMinTrackSize.x = 320; + info->ptMinTrackSize.y = 200; + } + break; + + case WM_POWERBROADCAST: + switch (wParam) + { + case PBT_APMQUERYSUSPEND: + if (!s_in_suspend && sample) + sample->OnSuspending(); + s_in_suspend = true; + return TRUE; + + case PBT_APMRESUMESUSPEND: + if (!s_minimized) + { + if (s_in_suspend && sample) + sample->OnResuming(); + s_in_suspend = false; + } + return TRUE; + } + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + + case WM_INPUT: + case WM_MOUSEHOVER: + Mouse::ProcessMessage(message, wParam, lParam); + break; + + case WM_KEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + Keyboard::ProcessMessage(message, wParam, lParam); + break; + + case WM_SYSKEYDOWN: + if (wParam == VK_RETURN && (lParam & 0x60000000) == 0x20000000) + { + // Implements the classic ALT+ENTER fullscreen toggle + if (s_fullscreen) + { + SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, 0); + + int width = 800; + int height = 600; + if (sample) + sample->GetDefaultSize(width, height); + + ShowWindow(hWnd, SW_SHOWNORMAL); + + SetWindowPos(hWnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + else + { + SetWindowLongPtr(hWnd, GWL_STYLE, 0); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST); + + SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + + ShowWindow(hWnd, SW_SHOWMAXIMIZED); + } + + s_fullscreen = !s_fullscreen; + } + Keyboard::ProcessMessage(message, wParam, lParam); + break; + + case WM_MOUSEACTIVATE: + // When you click activate the window, we want Mouse to ignore that event. + return MA_ACTIVATEANDEAT; + + case WM_MENUCHAR: + // A menu is active and the user presses a key that does not correspond + // to any mnemonic or accelerator key. Ignore so we don't produce an error beep. + return MAKELRESULT(0, MNC_CLOSE); +#endif + } + + return DefWindowProc(hWnd, message, wParam, lParam); +} + +// Exit helper +void ExitSample() noexcept +{ + PostQuitMessage(0); +} diff --git a/Tests/GDK/ManualTest.GDK/ManualTest.cpp b/Tests/GDK/ManualTest.GDK/ManualTest.cpp new file mode 100644 index 00000000..9f217674 --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/ManualTest.cpp @@ -0,0 +1,874 @@ +//-------------------------------------------------------------------------------------- +// ManualTest.cpp +// +// Advanced Technology Group (ATG) +// Copyright (C) Microsoft Corporation. All rights reserved. +//-------------------------------------------------------------------------------------- + +#include "pch.h" +#include "ManualTest.h" + +#include "ATGColors.h" +#include "FindMedia.h" +#include "StringUtil.h" +#include +#include +#include + +inline std::string DebugFormat(_In_z_ _Printf_format_string_ const char* format, ...) +{ +#ifdef _DEBUG + va_list args; + va_start(args, format); + + char buff[1024*10] = {}; + vsprintf_s(buff, format, args); + std::string str = buff; + va_end(args); + return buff; +#else + UNREFERENCED_PARAMETER(format); +#endif +} + + +extern void ExitSample() noexcept; + +using namespace DirectX; + +using Microsoft::WRL::ComPtr; + +Sample* g_sample = nullptr; + +namespace +{ + const char *ProgressStateText[] = { + u8"Unknown", + u8"Achieved", + u8"Not Started", + u8"In Progress" + }; + + const char *AchievementTypeText[] = { + u8"Unknown", + u8"All", + u8"Persistent", + u8"Challenge" + }; + + const int c_sampleUIPanel = 2000; + const int c_getAchievementsBtn = 2101; + const int c_getSingleAchievementBtn = 2102; + const int c_setSingleAchievementBtn = 2103; + const int c_getSingleAchievementBtn2 = 2104; + const int c_setSingleAchievement2Btn25 = 2105; + const int c_setSingleAchievement2Btn50 = 2106; + const int c_setSingleAchievement2Btn100 = 2107; +} + + +void CALLBACK MyXUserChangeEventCallback( + _In_opt_ void* context, + _In_ XUserLocalId userLocalId, + _In_ XUserChangeEvent event) +{ + UNREFERENCED_PARAMETER(context); + std::string eventType = ""; + switch (event) + { + case XUserChangeEvent::SignedInAgain: eventType = "SignedInAgain"; break; + case XUserChangeEvent::SigningOut: eventType = "SigningOut"; break; + case XUserChangeEvent::SignedOut: eventType = "SignedOut"; break; + case XUserChangeEvent::Gamertag: eventType = "Gamertag"; break; + case XUserChangeEvent::GamerPicture: eventType = "GamerPicture"; break; + case XUserChangeEvent::Privileges:eventType = "Privileges"; break; + default: break; + } + + XUserHandle handle; + uint64_t xuid = {}; + HRESULT hr = XUserFindUserByLocalId( + userLocalId, + &handle); + if (SUCCEEDED(hr)) + { + XUserGetId(handle, &xuid); + XUserCloseHandle(handle); + } + + g_sample->AddLog(DebugFormat("XUserChangeEventCallback: userLocalId: %ull = %s xuid: 0x%0.8x", userLocalId.value, eventType.c_str(), xuid)); +} + + +void __cdecl MyHCCallRoutedHandler( + _In_ HCCallHandle call, + _In_opt_ void* context + ) +{ + UNREFERENCED_PARAMETER(context); + const char* url = nullptr; + uint32_t status = 0; + HCHttpCallGetRequestUrl(call, &url); + HCHttpCallResponseGetStatusCode(call, &status); + HRESULT hrNet = S_OK; + uint32_t hrPlat = S_OK; + HCHttpCallResponseGetNetworkErrorCode(call, &hrNet, &hrPlat); + g_sample->AddLog(DebugFormat("HCCallRoutedHandler: %d [0x%0.8x] %s", status, hrNet, url)); +} + +void CALLBACK MyHCTraceCallback( + _In_z_ const char* areaName, + _In_ HCTraceLevel level, + _In_ uint64_t threadId, + _In_ uint64_t timestamp, + _In_z_ const char* message +) +{ + UNREFERENCED_PARAMETER(areaName); + UNREFERENCED_PARAMETER(level); + UNREFERENCED_PARAMETER(threadId); + UNREFERENCED_PARAMETER(timestamp); + g_sample->AddLog(DebugFormat("HC: %s", message)); +} + +void MyNetworkConnectivityChangedCallback(void* context, const XNetworkingConnectivityHint* /*hint*/) +{ + UNREFERENCED_PARAMETER(context); + // Always requery the latest network connectivity hint rather than relying on the passed parameter in case this is a stale notification + XNetworkingConnectivityHint hint{}; + HRESULT hr = XNetworkingGetConnectivityHint(&hint); + if (SUCCEEDED(hr)) + { + g_sample->AddLog(DebugFormat("NetworkConnectivityChangedCallback: networkInitialized: %d connectivityLevel: %d", hint.networkInitialized, hint.connectivityLevel)); + } + else + { + g_sample->AddLog(DebugFormat("NetworkConnectivityChangedCallback: 0x%0.8x", hr)); + } +} + + +Sample::Sample() noexcept(false) : + m_frame(0) +{ + g_sample = this; + m_deviceResources = std::make_unique(); + m_deviceResources->RegisterDeviceNotify(this); + m_liveInfoHUD = std::make_unique("Title-managed ManualTest Sample"); + + DX::ThrowIfFailed( + XTaskQueueCreate(XTaskQueueDispatchMode::ThreadPool, XTaskQueueDispatchMode::Manual, &m_mainAsyncQueue) + ); + + ATG::UIConfig uiconfig; + m_ui = std::make_unique(uiconfig); + m_log = std::make_unique(); + m_display = std::make_unique(); + + XTaskQueueRegistrationToken token = {}; + XUserRegisterForChangeEvent(nullptr, nullptr, MyXUserChangeEventCallback, &token); + + HCTraceSetClientCallback(MyHCTraceCallback); + m_liveInfoHUD->AddLog("**************** GAME START ****************"); + + XTaskQueueRegistrationToken tokenHint; + HRESULT hr = XNetworkingRegisterConnectivityHintChanged(nullptr, nullptr, MyNetworkConnectivityChangedCallback, &tokenHint); + m_liveInfoHUD->AddLog(DebugFormat("XNetworkingRegisterConnectivityHintChanged 0x%0.8x", hr)); +} + +Sample::~Sample() +{ + if (m_deviceResources) + { + m_deviceResources->WaitForGpu(); + } + + if (m_mainAsyncQueue) + { + XTaskQueueCloseHandle(m_mainAsyncQueue); + m_mainAsyncQueue = nullptr; + } + + XGameRuntimeUninitialize(); +} + +// Initialize the Direct3D resources required to run. +void Sample::Initialize(HWND window, int width, int height) +{ + m_gamePad = std::make_unique(); + + m_keyboard = std::make_unique(); + + m_mouse = std::make_unique(); + m_mouse->SetWindow(window); + + m_deviceResources->SetWindow(window, width, height); + + wchar_t result[MAX_PATH]; + DX::FindMediaFile(result, MAX_PATH, L".\\Assets\\SampleUI.csv"); + m_ui->LoadLayout(result, L".\\Assets\\"); + + HRESULT hr = XGameRuntimeInitialize(); + m_deviceResources->CreateDeviceResources(); + CreateDeviceDependentResources(); + + m_deviceResources->CreateWindowSizeDependentResources(); + CreateWindowSizeDependentResources(); + + m_liveInfoHUD->Initialize(); + + SetupUI(); +} + +void Sample::SetupUI() +{ +} + +#pragma region Frame Update +// Executes basic render loop. +void Sample::Tick() +{ + PIXBeginEvent(PIX_COLOR_DEFAULT, L"Frame %llu", m_frame); + + m_timer.Tick([&]() + { + Update(m_timer); + }); + + Render(); + + PIXEndEvent(); + m_frame++; +} + +static std::string NewGuid() +{ + GUID id = {}; + char buf[64] = {}; + + CoCreateGuid(&id); + + sprintf_s(buf, "%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX", + id.Data1, id.Data2, id.Data3, + id.Data4[0], id.Data4[1], id.Data4[2], id.Data4[3], + id.Data4[4], id.Data4[5], id.Data4[6], id.Data4[7]); + + return std::string(buf); +} + + +void Sample::CreateSession() +{ + if (m_xblContext == nullptr) + { + g_sample->m_liveInfoHUD->AddLog(DebugFormat("CreateSession no m_xblContext")); + return; + } + + if (m_userHandle1 == nullptr) + { + g_sample->m_liveInfoHUD->AddLog(DebugFormat("CreateSession Add user first")); + return; + } + + if (m_currentSessionHandle != nullptr) + { + g_sample->m_liveInfoHUD->AddLog(DebugFormat("CreateSession Session exists")); + return; + } + + XblMultiplayerSessionInitArgs initArgs; + initArgs.MaxMembersInSession = 8; //This matches our session template + initArgs.Visibility = XblMultiplayerSessionVisibility::Open; //The session is open and can be joined by anyone. + initArgs.InitiatorXuids = nullptr; + initArgs.InitiatorXuidsCount = 0; + initArgs.CustomJson = nullptr; + + const char* scid = nullptr; + XblGetScid(&scid); + std::string currentSessionName = NewGuid(); + XblMultiplayerSessionReference createdSessionRef = XblMultiplayerSessionReferenceCreate(scid, "GameSession", currentSessionName.c_str()); + + uint64_t userId = 0; + XUserGetId(m_userHandle1, &userId); + m_currentSessionHandle = XblMultiplayerSessionCreateHandle(userId, &createdSessionRef, &initArgs); + m_liveInfoHUD->AddLog(DebugFormat("m_currentSessionHandle 0x%0.8x", m_currentSessionHandle)); + + HRESULT hr = XblMultiplayerSessionJoin(m_currentSessionHandle, nullptr, true, true); + m_liveInfoHUD->AddLog(DebugFormat("XblMultiplayerSessionJoin 0x%0.8x", hr)); +} + +void Sample::WriteSession() +{ + if (m_xblContext == nullptr) + { + g_sample->m_liveInfoHUD->AddLog(DebugFormat("no m_xblContext")); + return; + } + if( m_currentSessionHandle == nullptr) + { + g_sample->m_liveInfoHUD->AddLog(DebugFormat("no m_currentSessionHandle")); + return; + } + + auto asyncBlock = std::make_unique(); + asyncBlock->queue = nullptr; + asyncBlock->context = nullptr; + asyncBlock->callback = [](XAsyncBlock* asyncBlock) + { + g_sample->m_liveInfoHUD->AddLog("***** XblMultiplayerWriteSessionAsync callback"); + + std::unique_ptr asyncBlockPtr{ asyncBlock }; //Take over ownership of the XAsyncBlock* + + XblMultiplayerSessionHandle createdHandle; + HRESULT hr = XblMultiplayerWriteSessionResult(asyncBlock, &createdHandle); + g_sample->m_liveInfoHUD->AddLog(DebugFormat("XblMultiplayerWriteSessionResult 0x%0.8x", hr)); + if (hr == S_OK) + { + XblWriteSessionStatus status = XblMultiplayerSessionWriteStatus(createdHandle); + g_sample->m_liveInfoHUD->AddLog(DebugFormat("XblMultiplayerSessionWriteStatus %d", status)); + } + + XblMultiplayerSessionCloseHandle(createdHandle); + }; + + HRESULT hr = XblMultiplayerWriteSessionAsync(m_xblContext, m_currentSessionHandle, XblMultiplayerSessionWriteMode::UpdateOrCreateNew, asyncBlock.get()); + m_liveInfoHUD->AddLog(DebugFormat("XblMultiplayerWriteSessionAsync 0x%0.8x", hr)); + if (SUCCEEDED(hr)) + { + // The call succeeded, so release the std::unique_ptr ownership of XAsyncBlock* since the callback will take over ownership. + // If the call fails, the std::unique_ptr will keep ownership and delete the XAsyncBlock* + asyncBlock.release(); + } + else + { + XblMultiplayerSessionCloseHandle(m_currentSessionHandle); + } +} + +// Updates the world. +void Sample::Update(DX::StepTimer const& timer) +{ + PIXBeginEvent(PIX_COLOR_DEFAULT, L"Update"); + + float elapsedTime = float(timer.GetElapsedSeconds()); + + auto pad = m_gamePad->GetState(0); + if (pad.IsConnected()) + { + m_gamePadButtons.Update(pad); + + if (pad.IsViewPressed()) + { + ExitSample(); + } + + if (m_gamePadButtons.menu == GamePad::ButtonStateTracker::PRESSED) + { + + } + + m_ui->Update(elapsedTime, pad); + } + else + { + m_gamePadButtons.Reset(); + } + + auto kb = m_keyboard->GetState(); + m_keyboardButtons.Update(kb); + + if (kb.Escape) + { + ExitSample(); + } + + bool isControlPressed = kb.LeftControl || kb.RightControl; + + if (!isControlPressed) + { + if ((m_keyboardButtons.IsKeyPressed(Keyboard::Keys::D1) && !m_keyboardButtonsLast.IsKeyPressed(Keyboard::Keys::D1))) + { + if (!m_xblInit) + { + m_xblInit = true; + uint32_t titleId = 0; + auto hr = XGameGetXboxTitleId(&titleId); + char scidBuffer[64] = {}; + sprintf_s(scidBuffer, "00000000-0000-0000-0000-0000%08X", titleId); + XblInitArgs xblInit = { nullptr, scidBuffer }; + hr = XblInitialize(&xblInit); + m_liveInfoHUD->AddLog(DebugFormat("XblInitialize 0x%0.8x", hr)); + hr = HCAddCallRoutedHandler(MyHCCallRoutedHandler, nullptr); + m_liveInfoHUD->AddLog(DebugFormat("HCAddCallRoutedHandler 0x%0.8x", hr)); + g_sample->AddLog(DebugFormat("XblInitialize 0x%0.8x done", hr)); + } + } + + if ((m_keyboardButtons.IsKeyPressed(Keyboard::Keys::D2) && !m_keyboardButtonsLast.IsKeyPressed(Keyboard::Keys::D2))) + { + if (m_userHandle1 == nullptr) + { + AddUser(XUserAddOptions::AddDefaultUserSilently); + g_sample->AddLog(DebugFormat("AddUser done")); + } + else + { + g_sample->AddLog(DebugFormat("no user")); + } + } + + if ((m_keyboardButtons.IsKeyPressed(Keyboard::Keys::D3) && !m_keyboardButtonsLast.IsKeyPressed(Keyboard::Keys::D3))) + { + if (m_xblInit && !m_xblRTA && m_xblContext) + { + HRESULT hr = XblMultiplayerSetSubscriptionsEnabled(m_xblContext, true); + g_sample->AddLog(DebugFormat("XblMultiplayerSetSubscriptionsEnabled 0x%0.8x", hr)); + + m_xblRTA = true; + hr = XblRealTimeActivityActivate(m_xblContext); + g_sample->AddLog(DebugFormat("XblRealTimeActivityActivate 0x%0.8x", hr)); + g_sample->AddLog(DebugFormat("RTA done")); + } + else + { + if(!m_xblInit) + g_sample->AddLog(DebugFormat("rta - not init")); + if (!m_xblContext) + g_sample->AddLog(DebugFormat("rta - not m_xblContext")); + } + } + + if ((m_keyboardButtons.IsKeyPressed(Keyboard::Keys::D4) && !m_keyboardButtonsLast.IsKeyPressed(Keyboard::Keys::D4))) + { + CreateSession(); + } + + if ((m_keyboardButtons.IsKeyPressed(Keyboard::Keys::D5) && !m_keyboardButtonsLast.IsKeyPressed(Keyboard::Keys::D5))) + { + WriteSession(); + } + } + else + { + if ((m_keyboardButtons.IsKeyPressed(Keyboard::Keys::D1) && !m_keyboardButtonsLast.IsKeyPressed(Keyboard::Keys::D1))) + { + if (m_xblInit) + { + m_xblInit = false; + auto async = new XAsyncBlock{}; + HRESULT hr = XblCleanupAsync(async); + g_sample->AddLog(DebugFormat("XblCleanupAsync 0x%0.8x", hr)); + XAsyncGetStatus(async, true); + g_sample->AddLog(DebugFormat("XblCleanupAsync 0x%0.8x done", hr)); + } + else + { + g_sample->AddLog(DebugFormat("cleanup - not init")); + } + } + + if ((m_keyboardButtons.IsKeyPressed(Keyboard::Keys::D2) && !m_keyboardButtonsLast.IsKeyPressed(Keyboard::Keys::D2))) + { + if (m_userHandle1 != nullptr) + { + uint64_t xuid = {}; + XUserGetId(m_userHandle1, &xuid); + XUserLocalId userLocalId = {}; + XUserGetLocalId(m_userHandle1, &userLocalId); + + g_sample->AddLog(DebugFormat("Closing XUserHandle 0x%0.8x. userLocalId: %ull, xuid: 0x%0.8x", m_userHandle1, userLocalId, xuid)); + SetUserHandle(nullptr); + } + else + { + g_sample->AddLog(DebugFormat("cleanup - no user")); + } + } + + if ((m_keyboardButtons.IsKeyPressed(Keyboard::Keys::D3) && !m_keyboardButtonsLast.IsKeyPressed(Keyboard::Keys::D3))) + { + if (m_xblInit && m_xblRTA) + { + m_xblRTA = false; + HRESULT hr = XblRealTimeActivityDeactivate(m_xblContext); + g_sample->AddLog(DebugFormat("XblRealTimeActivityDeactivate 0x%0.8x", hr)); + } + else + { + g_sample->AddLog(DebugFormat("cleanup - no session")); + } + } + + if ((m_keyboardButtons.IsKeyPressed(Keyboard::Keys::D4) && !m_keyboardButtonsLast.IsKeyPressed(Keyboard::Keys::D4))) + { + if (m_currentSessionHandle != nullptr) + { + XblMultiplayerSessionCloseHandle(m_currentSessionHandle); + g_sample->AddLog(DebugFormat("XblMultiplayerSessionCloseHandle")); + m_currentSessionHandle = nullptr; + } + else + { + g_sample->AddLog(DebugFormat("cleanup - no session")); + } + } + } + + m_keyboardButtonsLast = m_keyboardButtons; + + m_ui->Update(elapsedTime, *m_mouse, *m_keyboard); + + m_liveInfoHUD->Update(m_deviceResources->GetCommandQueue()); + + // Process any completed tasks + while (XTaskQueueDispatch(m_mainAsyncQueue, XTaskQueuePort::Completion, 0)) + { + SwitchToThread(); + } + + PIXEndEvent(); +} +#pragma endregion + +#pragma region Frame Render +// Draws the scene. +void Sample::Render() +{ + // Don't try to render anything before the first Update. + if (m_timer.GetFrameCount() == 0) + { + return; + } + + // Prepare the command list to render a new frame. + m_deviceResources->Prepare(); + Clear(); + + auto commandList = m_deviceResources->GetCommandList(); + PIXBeginEvent(commandList, PIX_COLOR_DEFAULT, L"Render"); + + ID3D12DescriptorHeap* heap = m_resourceDescriptors->Heap(); + commandList->SetDescriptorHeaps(1, &heap); + + m_liveInfoHUD->Render(commandList); + //m_log->Render(commandList); + //m_display->Render(commandList); + m_ui->Render(commandList); + + PIXEndEvent(commandList); + + // Show the new frame. + PIXBeginEvent(PIX_COLOR_DEFAULT, L"Present"); + m_deviceResources->Present(); + m_graphicsMemory->Commit(m_deviceResources->GetCommandQueue()); + PIXEndEvent(); +} + +// Helper method to clear the back buffers. +void Sample::Clear() +{ + auto commandList = m_deviceResources->GetCommandList(); + PIXBeginEvent(commandList, PIX_COLOR_DEFAULT, L"Clear"); + + // Clear the views. + auto rtvDescriptor = m_deviceResources->GetRenderTargetView(); + auto dsvDescriptor = m_deviceResources->GetDepthStencilView(); + + commandList->OMSetRenderTargets(1, &rtvDescriptor, FALSE, &dsvDescriptor); + commandList->ClearRenderTargetView(rtvDescriptor, ATG::Colors::Background, 0, nullptr); + commandList->ClearDepthStencilView(dsvDescriptor, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr); + + // Set the viewport and scissor rect. + auto viewport = m_deviceResources->GetScreenViewport(); + auto scissorRect = m_deviceResources->GetScissorRect(); + commandList->RSSetViewports(1, &viewport); + commandList->RSSetScissorRects(1, &scissorRect); + + PIXEndEvent(commandList); +} +#pragma endregion + +#pragma region Message Handlers +// Message handlers +void Sample::OnActivated() +{ +} + +void Sample::OnDeactivated() +{ +} + + +void Sample::AddLog(const std::string& str) +{ + m_liveInfoHUD->AddLog(str); +} + +void CALLBACK HandleXblRealTimeActivityConnectionStateChange( + _In_opt_ void* context, + _In_ XblRealTimeActivityConnectionState connectionState + ) +{ + UNREFERENCED_PARAMETER(context); + if (connectionState == XblRealTimeActivityConnectionState::Connected) + { + g_sample->AddLog(DebugFormat("RTA connected")); + } + if (connectionState == XblRealTimeActivityConnectionState::Connecting) + { + g_sample->AddLog(DebugFormat("RTA connecting")); + } + if (connectionState == XblRealTimeActivityConnectionState::Disconnected) + { + g_sample->AddLog(DebugFormat("RTA disconnected")); + } +} + +void Sample::SetUserHandle(XUserHandle user) +{ + if (m_userHandle1 != nullptr) + { + XblContextCloseHandle(m_xblContext); + XUserCloseHandle(m_userHandle1); + } + m_userHandle1 = user; + if (m_userHandle1 != nullptr) + { + HRESULT hr = XblContextCreateHandle(m_userHandle1, &m_xblContext); + if (FAILED(hr)) + { + g_sample->AddLog(DebugFormat("XblContextCreateHandle 0x%0.8x", hr)); + } + else + { + hr = XblRealTimeActivityAddConnectionStateChangeHandler(m_xblContext, HandleXblRealTimeActivityConnectionStateChange, nullptr); + g_sample->AddLog(DebugFormat("XblRealTimeActivityAddConnectionStateChangeHandler 0x%0.8x", hr)); + } + } +} + +HRESULT Sample::AddUser(XUserAddOptions options) +{ + // Attempt to get the default user, i.e. the user who launched the game + auto async = new XAsyncBlock{}; + + //async->context = (void*)options; + async->callback = [](XAsyncBlock* async) + { + //int x = static_cast(reinterpret_cast(async->context)); + //XUserAddOptions options = (XUserAddOptions)x; + XUserHandle user = nullptr; + HRESULT result = XUserAddResult(async, &user); + if (SUCCEEDED(result)) + { + // This failure doesn't come up until you try to actually do something with the user + uint64_t xuid = {}; + if (FAILED(result = XUserGetId(user, &xuid))) + { + g_sample->AddLog(DebugFormat("User ResolveUserIssueWithUI 0x%0.8x", result)); + //ResolveUserIssueWithUI(user); + } + else + { + XUserLocalId userLocalId = {}; + XUserGetLocalId(user, &userLocalId); + + g_sample->AddLog(DebugFormat("Got XUserHandle 0x%0.8x. userLocalId: %ull, xuid: 0x%0.8x", user, userLocalId.value, xuid)); + g_sample->SetUserHandle(user); + //OnSignInCompleted(ATG::XboxHandle{user}); + } + } + else if (result == E_GAMEUSER_RESOLVE_USER_ISSUE_REQUIRED + || result == E_GAMEUSER_NO_DEFAULT_USER + || result == static_cast(0x8015DC12)) + { + g_sample->AddLog(DebugFormat("User SwitchUser 0x%0.8x", result)); + g_sample->AddUser(XUserAddOptions::AllowGuests); + } + else + { + g_sample->AddLog(DebugFormat("User HandleError 0x%0.8x", result)); + //HandleError(result); + } + + delete async; + }; + + HRESULT hr = XUserAddAsync( + options, + async + ); + g_sample->AddLog(DebugFormat("XUserAddAsync 0x%0.8x", hr)); + + if (FAILED(hr)) + { + g_sample->AddLog("Unable to add user!"); + + delete async; + return hr; + } + + return S_OK; +} + +void Sample::OnSuspending() +{ + m_liveInfoHUD->AddLog("PLM resuming"); + m_deviceResources->Suspend(); +} + +void Sample::OnResuming() +{ + m_liveInfoHUD->AddLog("PLM resuming"); + //AddUser(XUserAddOptions::AddDefaultUserSilently); + + m_deviceResources->Resume(); + m_timer.ResetElapsedTime(); + m_gamePadButtons.Reset(); + m_keyboardButtons.Reset(); +} + +void Sample::OnWindowMoved() +{ + auto r = m_deviceResources->GetOutputSize(); + m_deviceResources->WindowSizeChanged(r.right, r.bottom); +} + +void Sample::OnWindowSizeChanged(int width, int height) +{ + if (!m_deviceResources->WindowSizeChanged(width, height)) + return; + + CreateWindowSizeDependentResources(); +} + +// Properties +void Sample::GetDefaultSize(int& width, int& height) const noexcept +{ + width = 1840; + height = 1035; +} +#pragma endregion + +#pragma region Direct3D Resources +// These are the resources that depend on the device. +void Sample::CreateDeviceDependentResources() +{ + auto device = m_deviceResources->GetD3DDevice(); + +#ifdef _GAMING_DESKTOP + D3D12_FEATURE_DATA_SHADER_MODEL shaderModel = { D3D_SHADER_MODEL_6_0 }; + if (FAILED(device->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel))) + || (shaderModel.HighestShaderModel < D3D_SHADER_MODEL_6_0)) + { +#ifdef _DEBUG + OutputDebugStringA("ERROR: Shader Model 6.0 is not supported!\n"); +#endif + throw std::runtime_error("Shader Model 6.0 is not supported!"); + } +#endif + + m_graphicsMemory = std::make_unique(device); + + RenderTargetState rtState(m_deviceResources->GetBackBufferFormat(), m_deviceResources->GetDepthBufferFormat()); + + m_resourceDescriptors = std::make_unique(device, + Descriptors::Count, + Descriptors::Reserve + ); + + ResourceUploadBatch resourceUpload(device); + resourceUpload.Begin(); + + m_liveInfoHUD->RestoreDevice(device, rtState, resourceUpload, *m_resourceDescriptors); + + wchar_t font[260]; + wchar_t background[260]; + + DX::FindMediaFile(font, 260, L"courier_16.spritefont"); + DX::FindMediaFile(background, 260, L"ATGSampleBackground.DDS"); + + m_log->RestoreDevice( + device, + resourceUpload, + rtState, + font, + background, + m_resourceDescriptors->GetCpuHandle(Descriptors::Font), + m_resourceDescriptors->GetGpuHandle(Descriptors::Font), + m_resourceDescriptors->GetCpuHandle(Descriptors::Background), + m_resourceDescriptors->GetGpuHandle(Descriptors::Background) + ); + + m_display->RestoreDevice( + device, + resourceUpload, + rtState, + font, + background, + m_resourceDescriptors->GetCpuHandle(Descriptors::ConsoleFont), + m_resourceDescriptors->GetGpuHandle(Descriptors::ConsoleFont), + m_resourceDescriptors->GetCpuHandle(Descriptors::ConsoleBackground), + m_resourceDescriptors->GetGpuHandle(Descriptors::ConsoleBackground) + ); + + m_ui->RestoreDevice(device, rtState, resourceUpload, *m_resourceDescriptors); + + auto uploadResourcesFinished = resourceUpload.End(m_deviceResources->GetCommandQueue()); + uploadResourcesFinished.wait(); +} + +void ScaleRect(const RECT &originalDisplayRect, const RECT &displayRect, const RECT &originalSubRect, RECT &scaledSubRect) +{ + const float widthScale = ((float)displayRect.right - (float)displayRect.left) / ((float)originalDisplayRect.right - (float)originalDisplayRect.left); + const float heightScale = ((float)displayRect.bottom - (float)displayRect.top) / ((float)originalDisplayRect.bottom - (float)originalDisplayRect.top); + + scaledSubRect.top = (LONG)((float)originalSubRect.top * heightScale); + scaledSubRect.left = (LONG)((float)originalSubRect.left * widthScale); + scaledSubRect.bottom = (LONG)((float)originalSubRect.bottom * heightScale); + scaledSubRect.right = (LONG)((float)originalSubRect.right * widthScale); +} + +// Allocate all memory resources that change on a window SizeChanged event. +void Sample::CreateWindowSizeDependentResources() +{ + RECT fullscreen = m_deviceResources->GetOutputSize(); + auto viewport = m_deviceResources->GetScreenViewport(); + + m_liveInfoHUD->SetViewport(viewport); + + // Scaled for 1920x1080 + static const RECT originalScale = { 0, 0, 1920, 1080 }; + static const RECT screenDisplay = { 960, 200, 1780, 450 }; + RECT scaledDisplay; + ScaleRect(originalScale, fullscreen, screenDisplay, scaledDisplay); + + m_log->SetWindow(scaledDisplay, false); + m_log->SetViewport(viewport); + + // Scaled for 1920x1080 + static const RECT screenDisplay2 = { 960, 500, 1780, 950 }; + ScaleRect(originalScale, fullscreen, screenDisplay2, scaledDisplay); + + m_display->SetWindow(scaledDisplay, false); + m_display->SetViewport(viewport); + + m_ui->SetWindow(fullscreen); +} + +void Sample::OnDeviceLost() +{ + m_ui->ReleaseDevice(); + m_graphicsMemory.reset(); + m_liveInfoHUD->ReleaseDevice(); + m_resourceDescriptors.reset(); +} + +void Sample::OnDeviceRestored() +{ + CreateDeviceDependentResources(); + + CreateWindowSizeDependentResources(); +} +#pragma endregion diff --git a/Tests/GDK/ManualTest.GDK/ManualTest.h b/Tests/GDK/ManualTest.GDK/ManualTest.h new file mode 100644 index 00000000..c724d810 --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/ManualTest.h @@ -0,0 +1,117 @@ +//-------------------------------------------------------------------------------------- +// ManualTest.h +// +// Advanced Technology Group (ATG) +// Copyright (C) Microsoft Corporation. All rights reserved. +//-------------------------------------------------------------------------------------- + +#pragma once + +#include "DeviceResources.h" +#include "LiveResources.h" +#include "SampleLiveInfoHUD.h" +#include "StepTimer.h" +#include "SampleGUI.h" +#include "TextConsole.h" + + +// A basic sample implementation that creates a D3D12 device and +// provides a render loop. +class Sample final : public DX::IDeviceNotify +{ +public: + + Sample() noexcept(false); + ~Sample(); + + Sample(Sample&&) = default; + Sample& operator= (Sample&&) = default; + + Sample(Sample const&) = delete; + Sample& operator= (Sample const&) = delete; + + // Initialization and management + void Initialize(HWND window, int width, int height); + + // Basic render loop + void Tick(); + + // IDeviceNotify + void OnDeviceLost() override; + void OnDeviceRestored() override; + + // Messages + void OnActivated(); + void OnDeactivated(); + void OnSuspending(); + void OnResuming(); + void OnWindowMoved(); + void OnWindowSizeChanged(int width, int height); + + // Properties + void GetDefaultSize(int& width, int& height) const noexcept; + + HRESULT AddUser(XUserAddOptions options); + void AddLog(const std::string& str); + void SetUserHandle(XUserHandle user); + void CreateSession(); + void WriteSession(); + +private: + + void Update(DX::StepTimer const& timer); + void Render(); + + void Clear(); + + void CreateDeviceDependentResources(); + void CreateWindowSizeDependentResources(); + + + void SetupUI(); + + // Device resources. + std::unique_ptr m_deviceResources; + + // Rendering loop timer. + uint64_t m_frame; + DX::StepTimer m_timer; + + // Input devices. + std::unique_ptr m_gamePad; + std::unique_ptr m_keyboard; + std::unique_ptr m_mouse; + + DirectX::GamePad::ButtonStateTracker m_gamePadButtons; + DirectX::Keyboard::KeyboardStateTracker m_keyboardButtons; + DirectX::Keyboard::KeyboardStateTracker m_keyboardButtonsLast; + + // DirectXTK objects. + std::unique_ptr m_graphicsMemory; + std::unique_ptr m_resourceDescriptors; + + std::unique_ptr m_liveInfoHUD; + + XTaskQueueHandle m_mainAsyncQueue; + + // UI Objects + std::unique_ptr m_ui; + std::unique_ptr m_log; + std::unique_ptr m_display; + + XUserHandle m_userHandle1 = nullptr; + XblContextHandle m_xblContext = nullptr; + bool m_xblInit = false; + bool m_xblRTA = false; + XblMultiplayerSessionHandle m_currentSessionHandle = nullptr; + + enum Descriptors + { + Font, + ConsoleFont, + Background, + ConsoleBackground, + Reserve, + Count = 32, + }; +}; diff --git a/Tests/GDK/ManualTest.GDK/ManualTest.sln b/Tests/GDK/ManualTest.GDK/ManualTest.sln new file mode 100644 index 00000000..480c15dc --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/ManualTest.sln @@ -0,0 +1,74 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32802.440 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ManualTest", "ManualTest.vcxproj", "{A501FE41-49C6-4ED4-9711-1C7231784849}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DirectXTK12", "..\APIRunner.GDK\Kits\DirectXTK12\DirectXTK12_GDK_2017.vcxproj", "{49F83A6D-D2A5-44C4-B84F-786E9F292499}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Gaming.Desktop.x64 = Debug|Gaming.Desktop.x64 + Debug|Gaming.Xbox.Scarlett.x64 = Debug|Gaming.Xbox.Scarlett.x64 + Debug|Gaming.Xbox.XboxOne.x64 = Debug|Gaming.Xbox.XboxOne.x64 + Profile|Gaming.Desktop.x64 = Profile|Gaming.Desktop.x64 + Profile|Gaming.Xbox.Scarlett.x64 = Profile|Gaming.Xbox.Scarlett.x64 + Profile|Gaming.Xbox.XboxOne.x64 = Profile|Gaming.Xbox.XboxOne.x64 + Release|Gaming.Desktop.x64 = Release|Gaming.Desktop.x64 + Release|Gaming.Xbox.Scarlett.x64 = Release|Gaming.Xbox.Scarlett.x64 + Release|Gaming.Xbox.XboxOne.x64 = Release|Gaming.Xbox.XboxOne.x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A501FE41-49C6-4ED4-9711-1C7231784849}.Debug|Gaming.Desktop.x64.ActiveCfg = Debug|Gaming.Desktop.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Debug|Gaming.Desktop.x64.Build.0 = Debug|Gaming.Desktop.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Debug|Gaming.Desktop.x64.Deploy.0 = Debug|Gaming.Desktop.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Debug|Gaming.Xbox.Scarlett.x64.ActiveCfg = Debug|Gaming.Xbox.Scarlett.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Debug|Gaming.Xbox.Scarlett.x64.Build.0 = Debug|Gaming.Xbox.Scarlett.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Debug|Gaming.Xbox.Scarlett.x64.Deploy.0 = Debug|Gaming.Xbox.Scarlett.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Debug|Gaming.Xbox.XboxOne.x64.ActiveCfg = Debug|Gaming.Xbox.XboxOne.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Debug|Gaming.Xbox.XboxOne.x64.Build.0 = Debug|Gaming.Xbox.XboxOne.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Debug|Gaming.Xbox.XboxOne.x64.Deploy.0 = Debug|Gaming.Xbox.XboxOne.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Profile|Gaming.Desktop.x64.ActiveCfg = Profile|Gaming.Desktop.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Profile|Gaming.Desktop.x64.Build.0 = Profile|Gaming.Desktop.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Profile|Gaming.Desktop.x64.Deploy.0 = Profile|Gaming.Desktop.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Profile|Gaming.Xbox.Scarlett.x64.ActiveCfg = Profile|Gaming.Xbox.Scarlett.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Profile|Gaming.Xbox.Scarlett.x64.Build.0 = Profile|Gaming.Xbox.Scarlett.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Profile|Gaming.Xbox.Scarlett.x64.Deploy.0 = Profile|Gaming.Xbox.Scarlett.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Profile|Gaming.Xbox.XboxOne.x64.ActiveCfg = Profile|Gaming.Xbox.XboxOne.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Profile|Gaming.Xbox.XboxOne.x64.Build.0 = Profile|Gaming.Xbox.XboxOne.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Profile|Gaming.Xbox.XboxOne.x64.Deploy.0 = Profile|Gaming.Xbox.XboxOne.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Release|Gaming.Desktop.x64.ActiveCfg = Release|Gaming.Desktop.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Release|Gaming.Desktop.x64.Build.0 = Release|Gaming.Desktop.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Release|Gaming.Desktop.x64.Deploy.0 = Release|Gaming.Desktop.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Release|Gaming.Xbox.Scarlett.x64.ActiveCfg = Release|Gaming.Xbox.Scarlett.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Release|Gaming.Xbox.Scarlett.x64.Build.0 = Release|Gaming.Xbox.Scarlett.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Release|Gaming.Xbox.Scarlett.x64.Deploy.0 = Release|Gaming.Xbox.Scarlett.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Release|Gaming.Xbox.XboxOne.x64.ActiveCfg = Release|Gaming.Xbox.XboxOne.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Release|Gaming.Xbox.XboxOne.x64.Build.0 = Release|Gaming.Xbox.XboxOne.x64 + {A501FE41-49C6-4ED4-9711-1C7231784849}.Release|Gaming.Xbox.XboxOne.x64.Deploy.0 = Release|Gaming.Xbox.XboxOne.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Debug|Gaming.Desktop.x64.ActiveCfg = Debug|Gaming.Desktop.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Debug|Gaming.Desktop.x64.Build.0 = Debug|Gaming.Desktop.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Debug|Gaming.Xbox.Scarlett.x64.ActiveCfg = Debug|Gaming.Xbox.Scarlett.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Debug|Gaming.Xbox.Scarlett.x64.Build.0 = Debug|Gaming.Xbox.Scarlett.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Debug|Gaming.Xbox.XboxOne.x64.ActiveCfg = Debug|Gaming.Xbox.XboxOne.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Debug|Gaming.Xbox.XboxOne.x64.Build.0 = Debug|Gaming.Xbox.XboxOne.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Profile|Gaming.Desktop.x64.ActiveCfg = Profile|Gaming.Desktop.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Profile|Gaming.Desktop.x64.Build.0 = Profile|Gaming.Desktop.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Profile|Gaming.Xbox.Scarlett.x64.ActiveCfg = Profile|Gaming.Xbox.Scarlett.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Profile|Gaming.Xbox.Scarlett.x64.Build.0 = Profile|Gaming.Xbox.Scarlett.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Profile|Gaming.Xbox.XboxOne.x64.ActiveCfg = Profile|Gaming.Xbox.XboxOne.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Profile|Gaming.Xbox.XboxOne.x64.Build.0 = Profile|Gaming.Xbox.XboxOne.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Release|Gaming.Desktop.x64.ActiveCfg = Release|Gaming.Desktop.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Release|Gaming.Desktop.x64.Build.0 = Release|Gaming.Desktop.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Release|Gaming.Xbox.Scarlett.x64.ActiveCfg = Release|Gaming.Xbox.Scarlett.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Release|Gaming.Xbox.Scarlett.x64.Build.0 = Release|Gaming.Xbox.Scarlett.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Release|Gaming.Xbox.XboxOne.x64.ActiveCfg = Release|Gaming.Xbox.XboxOne.x64 + {49F83A6D-D2A5-44C4-B84F-786E9F292499}.Release|Gaming.Xbox.XboxOne.x64.Build.0 = Release|Gaming.Xbox.XboxOne.x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {613F8626-6BFE-415A-A22D-4BA72EC62705} + EndGlobalSection +EndGlobal diff --git a/Tests/GDK/ManualTest.GDK/ManualTest.vcxproj b/Tests/GDK/ManualTest.GDK/ManualTest.vcxproj new file mode 100644 index 00000000..abcd474e --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/ManualTest.vcxproj @@ -0,0 +1,592 @@ + + + + + Debug + Gaming.Xbox.Scarlett.x64 + + + Profile + Gaming.Xbox.Scarlett.x64 + + + Release + Gaming.Xbox.Scarlett.x64 + + + Release + Gaming.Xbox.XboxOne.x64 + + + Profile + Gaming.Xbox.XboxOne.x64 + + + Debug + Gaming.Xbox.XboxOne.x64 + + + Release + Gaming.Desktop.x64 + + + Profile + Gaming.Desktop.x64 + + + Debug + Gaming.Desktop.x64 + + + + ManualTest + {a501fe41-49c6-4ed4-9711-1c7231784849} + en-US + Win32Proj + + 15.0 + Native + Xbox.Services.API.C + x64 + + + + + Application + v142 + false + true + Unicode + false + false + + + Application + v142 + false + true + Unicode + false + false + + + Application + v142 + false + true + Unicode + false + false + + + Application + v142 + false + true + Unicode + false + false + + + Application + v142 + true + Unicode + false + false + + + Application + v142 + true + Unicode + false + false + + + Application + v142 + false + true + Unicode + + + Application + v142 + false + true + Unicode + + + Application + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(Console_SdkLibPath);$(Console_SdkWindowsMetadataPath) + $(Console_SdkLibPath) + $(Console_SdkLibPath);$(Console_SdkWindowsMetadataPath) + $(Console_SdkIncludeRoot) + $(Console_SdkRoot)bin;$(Console_SdkToolPath);$(ExecutablePath) + false + + + $(Console_SdkLibPath);$(Console_SdkWindowsMetadataPath) + $(Console_SdkLibPath) + $(Console_SdkLibPath);$(Console_SdkWindowsMetadataPath) + $(Console_SdkIncludeRoot) + $(Console_SdkRoot)bin;$(Console_SdkToolPath);$(ExecutablePath) + false + + + $(Console_SdkLibPath);$(Console_SdkWindowsMetadataPath) + $(Console_SdkLibPath) + $(Console_SdkLibPath);$(Console_SdkWindowsMetadataPath) + $(Console_SdkIncludeRoot) + $(Console_SdkRoot)bin;$(Console_SdkToolPath);$(ExecutablePath) + false + + + $(Console_SdkLibPath);$(Console_SdkWindowsMetadataPath) + $(Console_SdkLibPath) + $(Console_SdkLibPath);$(Console_SdkWindowsMetadataPath) + $(Console_SdkIncludeRoot) + $(Console_SdkRoot)bin;$(Console_SdkToolPath);$(ExecutablePath) + false + + + $(Console_SdkLibPath);$(Console_SdkWindowsMetadataPath) + $(Console_SdkLibPath) + $(Console_SdkLibPath);$(Console_SdkWindowsMetadataPath) + $(Console_SdkIncludeRoot) + $(Console_SdkRoot)bin;$(Console_SdkToolPath);$(ExecutablePath) + true + + + $(Console_SdkLibPath);$(Console_SdkWindowsMetadataPath) + $(Console_SdkLibPath) + $(Console_SdkLibPath);$(Console_SdkWindowsMetadataPath) + $(Console_SdkIncludeRoot) + $(Console_SdkRoot)bin;$(Console_SdkToolPath);$(ExecutablePath) + true + + + $(Console_SdkLibPath);$(LibraryPath) + $(Console_SdkIncludeRoot);$(IncludePath) + $(Console_SdkRoot)bin;$(Console_SdkToolPath);$(ExecutablePath) + false + + + $(Console_SdkLibPath);$(LibraryPath) + $(Console_SdkIncludeRoot);$(IncludePath) + $(Console_SdkRoot)bin;$(Console_SdkToolPath);$(ExecutablePath) + false + + + $(Console_SdkLibPath);$(LibraryPath) + $(Console_SdkIncludeRoot);$(IncludePath) + $(Console_SdkRoot)bin;$(Console_SdkToolPath);$(ExecutablePath) + true + + + + uuid.lib;$(Console_Libs);%(XboxExtensionsDependencies);%(AdditionalDependencies) + true + Windows + true + true + + + Use + pch.h + $(ProjectDir);..\APIRunner.GDK\Kits\LiveTK;..\APIRunner.GDK\Kits\DirectXTK12\Inc;..\APIRunner.GDK\Kits\ATGTK;..\APIRunner.GDK\Kits\ATGTelemetry\GDK;%(AdditionalIncludeDirectories) + + + MaxSpeed + ATG_ENABLE_TELEMETRY;NDEBUG;%(PreprocessorDefinitions) + Level4 + true + true + true + /Zc:__cplusplus %(AdditionalOptions) + 5204 + + + + + uuid.lib;$(Console_Libs);%(XboxExtensionsDependencies);%(AdditionalDependencies) + true + Windows + true + true + + + Use + pch.h + $(ProjectDir);..\APIRunner.GDK\Kits\LiveTK;..\APIRunner.GDK\Kits\DirectXTK12\Inc;..\APIRunner.GDK\Kits\ATGTK;..\APIRunner.GDK\Kits\ATGTelemetry\GDK;%(AdditionalIncludeDirectories) + + + + + MaxSpeed + ATG_ENABLE_TELEMETRY;NDEBUG;%(PreprocessorDefinitions) + Level4 + true + true + true + /Zc:__cplusplus %(AdditionalOptions) + 5204 + + + + + uuid.lib;$(Console_Libs);%(XboxExtensionsDependencies);%(AdditionalDependencies) + true + Windows + true + true + + + Use + pch.h + $(ProjectDir);..\APIRunner.GDK\Kits\LiveTK;..\APIRunner.GDK\Kits\DirectXTK12\Inc;..\APIRunner.GDK\Kits\ATGTK;..\APIRunner.GDK\Kits\ATGTelemetry\GDK;%(AdditionalIncludeDirectories) + + + MaxSpeed + ATG_ENABLE_TELEMETRY;NDEBUG;PROFILE;%(PreprocessorDefinitions) + Level4 + true + true + true + /Zc:__cplusplus %(AdditionalOptions) + 5204 + + + + + uuid.lib;$(Console_Libs);%(XboxExtensionsDependencies);%(AdditionalDependencies) + true + Windows + true + true + + + Use + pch.h + $(ProjectDir);..\APIRunner.GDK\Kits\LiveTK;..\APIRunner.GDK\Kits\DirectXTK12\Inc;..\APIRunner.GDK\Kits\ATGTK;..\APIRunner.GDK\Kits\ATGTelemetry\GDK;%(AdditionalIncludeDirectories) + + + + + MaxSpeed + ATG_ENABLE_TELEMETRY;NDEBUG;PROFILE;%(PreprocessorDefinitions) + Level4 + true + true + true + /Zc:__cplusplus %(AdditionalOptions) + 5204 + + + + + uuid.lib;$(Console_Libs);%(XboxExtensionsDependencies);%(AdditionalDependencies) + Windows + true + + + pch.h + Use + false + $(ProjectDir);..\APIRunner.GDK\Kits\LiveTK;..\APIRunner.GDK\Kits\DirectXTK12\Inc;..\APIRunner.GDK\Kits\ATGTK;..\APIRunner.GDK\Kits\ATGTelemetry\GDK;%(AdditionalIncludeDirectories) + + + Level4 + Disabled + ATG_ENABLE_TELEMETRY;_DEBUG;%(PreprocessorDefinitions) + true + /Zc:__cplusplus %(AdditionalOptions) + 5204 + + + + + uuid.lib;$(Console_Libs);%(XboxExtensionsDependencies);%(AdditionalDependencies) + Windows + true + + + pch.h + Use + false + $(ProjectDir);..\APIRunner.GDK\Kits\LiveTK;..\APIRunner.GDK\Kits\DirectXTK12\Inc;..\APIRunner.GDK\Kits\ATGTK;..\APIRunner.GDK\Kits\ATGTelemetry\GDK;%(AdditionalIncludeDirectories) + + + + + Level4 + Disabled + ATG_ENABLE_TELEMETRY;_DEBUG;%(PreprocessorDefinitions) + true + /Zc:__cplusplus %(AdditionalOptions) + 5204 + + + + + true + Windows + true + true + uuid.lib;$(Console_Libs);%(AdditionalDependencies) + + + PerMonitorHighDPIAware + + + Use + pch.h + $(ProjectDir);..\APIRunner.GDK\Kits\LiveTK;..\APIRunner.GDK\Kits\DirectXTK12\Inc;..\APIRunner.GDK\Kits\ATGTK;..\APIRunner.GDK\Kits\ATGTelemetry\GDK;%(AdditionalIncludeDirectories) + MaxSpeed + ATG_ENABLE_TELEMETRY;NDEBUG;__WRL_NO_DEFAULT_LIB__;%(PreprocessorDefinitions) + Level4 + true + true + true + /Zc:__cplusplus %(AdditionalOptions) + 5204 + + + 6.0 + true + /Fd "$(OutDir)%(Filename).pdb" %(AdditionalOptions) + + + + + true + Windows + true + true + uuid.lib;$(Console_Libs);%(AdditionalDependencies) + + + PerMonitorHighDPIAware + + + Use + pch.h + $(ProjectDir);..\APIRunner.GDK\Kits\LiveTK;..\APIRunner.GDK\Kits\DirectXTK12\Inc;..\APIRunner.GDK\Kits\ATGTK;..\APIRunner.GDK\Kits\ATGTelemetry\GDK;%(AdditionalIncludeDirectories) + MaxSpeed + ATG_ENABLE_TELEMETRY;NDEBUG;__WRL_NO_DEFAULT_LIB__;PROFILE;%(PreprocessorDefinitions) + Level4 + true + true + true + /Zc:__cplusplus %(AdditionalOptions) + 5204 + + + 6.0 + true + /Fd "$(OutDir)%(Filename).pdb" %(AdditionalOptions) + + + + + Windows + true + uuid.lib;$(Console_Libs);%(AdditionalDependencies) + + + PerMonitorHighDPIAware + + + pch.h + Use + false + $(ProjectDir);..\APIRunner.GDK\Kits\LiveTK;..\APIRunner.GDK\Kits\DirectXTK12\Inc;..\APIRunner.GDK\Kits\ATGTK;..\APIRunner.GDK\Kits\ATGTelemetry\GDK;%(AdditionalIncludeDirectories) + Level4 + Disabled + ATG_ENABLE_TELEMETRY;_DEBUG;__WRL_NO_DEFAULT_LIB__;%(PreprocessorDefinitions) + true + /Zc:__cplusplus %(AdditionalOptions) + 5204 + + + 6.0 + true + /Fd "$(OutDir)%(Filename).pdb" %(AdditionalOptions) + + + + + $(OutDir)Assets + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + Create + Create + Create + Create + Create + + + + + + + + + true + + + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + {49f83a6d-d2a5-44c4-b84f-786e9f292499} + + + + + + \ No newline at end of file diff --git a/Tests/GDK/ManualTest.GDK/ManualTest.vcxproj.filters b/Tests/GDK/ManualTest.GDK/ManualTest.vcxproj.filters new file mode 100644 index 00000000..665b4ba2 --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/ManualTest.vcxproj.filters @@ -0,0 +1,125 @@ + + + + + d0a754c7-880c-4d67-8324-08611b7402c1 + + + d99f4f2b-95af-447b-9a87-a5636e45af50 + ico;cur;bmp;dds;dlg;fbx;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tga;tiff;tif;png;wav;mfcribbon-ms + + + 655550be-56fc-4265-8a51-5c51c987dea5 + + + 003f9d58-a022-48a0-bad6-143a8dfa78c2 + + + 396f274a-6b80-4569-a46f-bc59173d9d04 + + + {b6361f05-2e9f-4f2f-b537-15bf9ca05024} + + + + + + + Common + + + Common + + + + + + + + + + + + + + + + + + + Common + + + + + + + + + + + + Assets + + + + + + + + + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + + + Assets + + + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + \ No newline at end of file diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/Courier_16.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/Courier_16.spritefont new file mode 100644 index 00000000..c9377e95 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/Courier_16.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/Courier_36.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/Courier_36.spritefont new file mode 100644 index 00000000..986333eb Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/Courier_36.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_18.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_18.spritefont new file mode 100644 index 00000000..8b64463e Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_18.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_18_Bold.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_18_Bold.spritefont new file mode 100644 index 00000000..cd05fa17 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_18_Bold.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_18_Italic.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_18_Italic.spritefont new file mode 100644 index 00000000..6553d839 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_18_Italic.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_22.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_22.spritefont new file mode 100644 index 00000000..61bac081 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_22.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_22_Bold.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_22_Bold.spritefont new file mode 100644 index 00000000..e635bd01 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_22_Bold.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_22_Italic.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_22_Italic.spritefont new file mode 100644 index 00000000..c9cb3d04 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_22_Italic.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_36.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_36.spritefont new file mode 100644 index 00000000..af302dde Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_36.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_36_Bold.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_36_Bold.spritefont new file mode 100644 index 00000000..2a7a0524 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_36_Bold.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_36_Italic.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_36_Italic.spritefont new file mode 100644 index 00000000..271b06b4 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/SegoeUI_36_Italic.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/XboxOneController.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/XboxOneController.spritefont new file mode 100644 index 00000000..37953a60 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/XboxOneController.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/XboxOneControllerLegend.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/XboxOneControllerLegend.spritefont new file mode 100644 index 00000000..d4207854 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/XboxOneControllerLegend.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/XboxOneControllerLegendSmall.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/XboxOneControllerLegendSmall.spritefont new file mode 100644 index 00000000..0247b8ab Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/XboxOneControllerLegendSmall.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Fonts/XboxOneControllerSmall.spritefont b/Tests/GDK/ManualTest.GDK/Media/Fonts/XboxOneControllerSmall.spritefont new file mode 100644 index 00000000..839ef6cb Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Fonts/XboxOneControllerSmall.spritefont differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Textures/ATGSampleBackground.DDS b/Tests/GDK/ManualTest.GDK/Media/Textures/ATGSampleBackground.DDS new file mode 100644 index 00000000..dde4e8c0 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Textures/ATGSampleBackground.DDS differ diff --git a/Tests/GDK/ManualTest.GDK/Media/Textures/GamerPic.png b/Tests/GDK/ManualTest.GDK/Media/Textures/GamerPic.png new file mode 100644 index 00000000..cb3d71b7 Binary files /dev/null and b/Tests/GDK/ManualTest.GDK/Media/Textures/GamerPic.png differ diff --git a/Tests/GDK/ManualTest.GDK/MicrosoftGameConfig.mgc b/Tests/GDK/ManualTest.GDK/MicrosoftGameConfig.mgc new file mode 100644 index 00000000..3b8ccdcb --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/MicrosoftGameConfig.mgc @@ -0,0 +1,25 @@ + + + + + + + + + + + 76b1590E + 0000000044264AE3 + + + diff --git a/Tests/GDK/ManualTest.GDK/SampleLiveInfoHUD.cpp b/Tests/GDK/ManualTest.GDK/SampleLiveInfoHUD.cpp new file mode 100644 index 00000000..f67d2b3e --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/SampleLiveInfoHUD.cpp @@ -0,0 +1,341 @@ +//-------------------------------------------------------------------------------------- +// File: LiveInfoHUD.cpp +// +// A Heads Up Display (HUD) for Xbox Live samples +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------------- +#include "pch.h" +#include "SampleLiveInfoHUD.h" + +#include +#include + +#include "DescriptorHeap.h" +#include "DirectXHelpers.h" +#include "ResourceUploadBatch.h" +#include "WICTextureLoader.h" + +#include "ATGColors.h" +#include "FindMedia.h" + +using namespace ATG; +using namespace DirectX; + +namespace +{ + const size_t c_GamerPicBuffer = 1024 * 16; + constexpr float c_StatusBarCoordinate = 1010.0f; + constexpr float c_HeaderBarCoordinate = 10.0f; + constexpr long c_GamerPicCoordinate = long(c_HeaderBarCoordinate) + 11; +} + +_Use_decl_annotations_ +SampleLiveInfoHUD::SampleLiveInfoHUD(char const* sampleTitle) noexcept(false) : + m_sampleTitle(sampleTitle), + m_gamerTag("No User Signed in"), + m_scaleWidth(1.f), + m_scaleHeight(1.f), + m_gamerPicCPU{}, + m_gamerPicGPU{}, + m_gamerPicDataSize(0), + m_gamerPicReady(false) +{ + +} + +void SampleLiveInfoHUD::Initialize(int windowWidth, int windowHeight) +{ + UNREFERENCED_PARAMETER(windowWidth); + UNREFERENCED_PARAMETER(windowHeight); + + uint32_t titleId = {}; + HRESULT hr = XGameGetXboxTitleId(&titleId); + + if (SUCCEEDED(hr)) + { + char hexTitleId[16] = {}; + sprintf_s(hexTitleId, "0x%08X", titleId); + m_titleId.assign(hexTitleId); + + char scidBuffer[64] = {}; + sprintf_s(scidBuffer, "00000000-0000-0000-0000-0000%08x", titleId); + m_serviceConfigId = scidBuffer; + } + else + { + m_titleId = "Not Set"; + m_serviceConfigId = "Not Set"; + } + + char sandboxId[XSystemXboxLiveSandboxIdMaxBytes] = {}; + XSystemGetXboxLiveSandboxId(XSystemXboxLiveSandboxIdMaxBytes, sandboxId, nullptr); + m_sandboxId = sandboxId; +} + +void SampleLiveInfoHUD::Update(_In_ ID3D12CommandQueue* commandQueue) +{ + if (!m_gamerPicReady || !m_device) + return; + + CreateShaderResourceView(m_device.Get(), m_gamerDefaultPic.Get(), m_gamerPicCPU); + + if (m_gamerPicDataSize && m_gamerPicData) + { + ResourceUploadBatch upload(m_device.Get()); + + upload.Begin(); + + if (SUCCEEDED(CreateWICTextureFromMemory(m_device.Get(), upload, m_gamerPicData.get(), m_gamerPicDataSize, m_gamerPic.ReleaseAndGetAddressOf()))) + { + CreateShaderResourceView(m_device.Get(), m_gamerPic.Get(), m_gamerPicCPU); + } + + auto result = upload.End(commandQueue); + result.wait(); + } + + m_gamerPicReady = false; +} + +void SampleLiveInfoHUD::ReleaseDevice() +{ + m_gamerPic.Reset(); + m_gamerDefaultPic.Reset(); + + m_device.Reset(); + + m_batch.reset(); + m_smallFont.reset(); + m_boldFont.reset(); + m_titleFont.reset(); + + m_gamerPicCPU = {}; + m_gamerPicGPU = {}; +} + +_Use_decl_annotations_ +void SampleLiveInfoHUD::RestoreDevice( + ID3D12Device* device, + const RenderTargetState& renderTarget, + ResourceUploadBatch& resourceUpload, + DescriptorPile& pile) +{ + m_device = device; + + SpriteBatchPipelineStateDescription pd(renderTarget); + m_batch = std::make_unique(device, resourceUpload, pd); + + wchar_t buff[MAX_PATH] = {}; + + DX::FindMediaFile(buff, MAX_PATH, L"SegoeUI_18.spritefont"); + size_t index = pile.Allocate(); + m_smallFont = std::make_unique(device, resourceUpload, buff, pile.GetCpuHandle(index), pile.GetGpuHandle(index)); + + DX::FindMediaFile(buff, MAX_PATH, L"SegoeUI_18_Bold.spritefont"); + index = pile.Allocate(); + m_boldFont = std::make_unique(device, resourceUpload, buff, pile.GetCpuHandle(index), pile.GetGpuHandle(index)); + + DX::FindMediaFile(buff, MAX_PATH, L"SegoeUI_36.spritefont"); + index = pile.Allocate(); + m_titleFont = std::make_unique(device, resourceUpload, buff, pile.GetCpuHandle(index), pile.GetGpuHandle(index)); + + DX::FindMediaFile(buff, MAX_PATH, L"GamerPic.png"); + DX::ThrowIfFailed( + CreateWICTextureFromFile(device, resourceUpload, buff, m_gamerDefaultPic.ReleaseAndGetAddressOf()) + ); + + index = pile.Allocate(); + m_gamerPicCPU = pile.GetCpuHandle(index); + m_gamerPicGPU = pile.GetGpuHandle(index); + + if (m_gamerPicDataSize > 0 && m_gamerPicData) + { + m_gamerPicReady = true; + } + + CreateShaderResourceView(device, m_gamerDefaultPic.Get(), m_gamerPicCPU); +} + +_Use_decl_annotations_ +void SampleLiveInfoHUD::SetUser(XUserHandle user, XTaskQueueHandle queue) +{ + if (!user) + { + m_gamerTag = "No User Signed in"; + + m_gamerPicDataSize = 0; + m_gamerPicData.reset(); + + CreateShaderResourceView(m_device.Get(), m_gamerDefaultPic.Get(), m_gamerPicCPU); + } + else + { + m_gamerTag.resize(XUserGamertagComponentClassicMaxBytes); + if (FAILED(XUserGetGamertag(user, XUserGamertagComponent::Classic, XUserGamertagComponentClassicMaxBytes, &m_gamerTag[0], nullptr))) + { + m_gamerTag = "***ERROR***"; + } + + auto async = new XAsyncBlock{}; + async->context = this; + async->queue = queue; + async->callback = [](XAsyncBlock *async) + { + auto pThis = reinterpret_cast(async->context); + + pThis->m_gamerPicData.reset(new uint8_t[c_GamerPicBuffer]); + size_t bufferFilled = 0; + + HRESULT hr = XUserGetGamerPictureResult(async, c_GamerPicBuffer, pThis->m_gamerPicData.get(), &bufferFilled); + if (SUCCEEDED(hr)) + { + pThis->m_gamerPicDataSize = bufferFilled; + pThis->m_gamerPicReady = true; + } + else + { + pThis->m_gamerPicDataSize = 0; + pThis->m_gamerPicData.reset(); + } + + delete async; + }; + + DX::ThrowIfFailed(XUserGetGamerPictureAsync(user, XUserGamerPictureSize::Small, async)); + } +} + +void SampleLiveInfoHUD::ClearLog() +{ + std::lock_guard lock(m_logLinesMutex); + m_logLines.clear(); +} + +void WriteLogToFile(const std::string& strIn) +{ + HANDLE hFile; + std::string str = strIn; + str += "\r\n"; + DWORD dwBytesToWrite = (DWORD) str.length(); + DWORD dwBytesWritten = 0; + BOOL bErrorFlag = FALSE; + + hFile = CreateFile(L"D:\\EventsLog.txt", FILE_APPEND_DATA, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + return; + } + + bErrorFlag = WriteFile( + hFile, // open file handle + str.data(), // start of data to write + dwBytesToWrite, // number of bytes to write + &dwBytesWritten, // number of bytes that were written + NULL); // no overlapped structure + + if (FALSE == bErrorFlag) + { + } + else + { + if (dwBytesWritten != dwBytesToWrite) + { + //printf("Error: dwBytesWritten != dwBytesToWrite\n"); + } + else + { + //printf("Wrote %d bytes to EventsLog.txt successfully.\n", dwBytesWritten); + } + } + + CloseHandle(hFile); +} + +void SampleLiveInfoHUD::AddLog(const std::string& strIn) +{ + std::lock_guard lock(m_logLinesMutex); + + SYSTEMTIME st; + GetLocalTime(&st); + char sz[255]; + sprintf_s(sz, 255, "[%0.2d/%0.2d %0.2d:%0.2d:%0.2d:%0.4d] ", + st.wMonth, st.wDay, + st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); + + std::string strTime = sz; + std::string str = strTime + strIn; + + for (size_t i = 0; i < str.length(); i++) + { + if (static_cast(str[i]) > 0x80) // ignore invalid chars since font can't render + { + str[i] = ' '; + } + } + + OutputDebugStringA(str.c_str()); + OutputDebugStringA("\n"); + + if (m_logLines.size() > 27) + { + m_logLines.erase(m_logLines.begin()); + } + m_logLines.push_back(str); + WriteLogToFile(str); + +} + +_Use_decl_annotations_ +void SampleLiveInfoHUD::Render(ID3D12GraphicsCommandList* commandList) +{ + m_batch->Begin( + commandList, + SpriteSortMode_Deferred, + DirectX::XMMatrixAffineTransformation2D(XMVectorSet(m_scaleWidth, m_scaleHeight, 0, 0), XMVectorZero(), 0.f, XMVectorZero()) + ); + + if(m_logLines.size() > 0) + { + std::lock_guard lock(m_logLinesMutex); + float y = 100.0f; + for (auto& s : m_logLines) + { + m_smallFont->DrawString(m_batch.get(), s.c_str(), XMFLOAT2(60.f, y), ATG::Colors::White, 0.0f); + y += 30.0f; + } + } + + float y = 800.0f; + float x = 1210.0f; + m_smallFont->DrawString(m_batch.get(), "Press 1 for XblInit. Ctrl+1 for XblCleanup", XMFLOAT2(x, y += 30.0f), ATG::Colors::OffWhite, 0.0f); + m_smallFont->DrawString(m_batch.get(), "Press 2 for XUserAdd. Ctrl+2 for UserClose", XMFLOAT2(x, y += 30.0f), ATG::Colors::OffWhite, 0.0f); + m_smallFont->DrawString(m_batch.get(), "Press 3 for RTA on. Ctrl+3 for RTA off", XMFLOAT2(x, y += 30.0f), ATG::Colors::OffWhite, 0.0f); + m_smallFont->DrawString(m_batch.get(), "Press 4 for CreateSession. Ctrl+4 for session close", XMFLOAT2(x, y += 30.0f), ATG::Colors::OffWhite, 0.0f); + m_smallFont->DrawString(m_batch.get(), "Press 5 for WriteSession", XMFLOAT2(x, y += 30.0f), ATG::Colors::OffWhite, 0.0f); + + m_boldFont->DrawString(m_batch.get(), "Sandbox Id:", XMFLOAT2(270.f, c_StatusBarCoordinate), ATG::Colors::OffWhite, 0.0f); + m_smallFont->DrawString(m_batch.get(), m_sandboxId.c_str(), XMFLOAT2(410.f, c_StatusBarCoordinate), ATG::Colors::OffWhite, 0.0f); + + m_boldFont->DrawString(m_batch.get(), "Title Id:", XMFLOAT2(590.f, c_StatusBarCoordinate), ATG::Colors::OffWhite, 0.0f); + m_smallFont->DrawString(m_batch.get(), m_titleId.c_str(), XMFLOAT2(680.f, c_StatusBarCoordinate), ATG::Colors::OffWhite, 0.0f); + + m_boldFont->DrawString(m_batch.get(), "Service Config Id:", XMFLOAT2(950.f, c_StatusBarCoordinate), ATG::Colors::OffWhite, 0.0f); + m_smallFont->DrawString(m_batch.get(), m_serviceConfigId.c_str(), XMFLOAT2(1155.f, c_StatusBarCoordinate), ATG::Colors::OffWhite, 0.0f); + + m_batch->End(); +} + +void SampleLiveInfoHUD::SetViewport(const D3D12_VIEWPORT &viewport) +{ + if (m_batch) + { + m_batch->SetViewport(viewport); + SetWindowSize(viewport.Width, viewport.Height); + } +} diff --git a/Tests/GDK/ManualTest.GDK/SampleLiveInfoHUD.h b/Tests/GDK/ManualTest.GDK/SampleLiveInfoHUD.h new file mode 100644 index 00000000..0c111650 --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/SampleLiveInfoHUD.h @@ -0,0 +1,90 @@ +//-------------------------------------------------------------------------------------- +// File: LiveInfoHUD.h +// +// A Heads Up Display (HUD) for Xbox Live samples +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------------- +#pragma once + +#include +#include +#include + +#include + +#include "SpriteBatch.h" +#include "SpriteFont.h" + +namespace ATG +{ + class SampleLiveInfoHUD + { + public: + explicit SampleLiveInfoHUD(_In_ char const* sampleTitle) noexcept(false); + + void Initialize(int windowWidth = 0, int windowHeight = 0); + + void Update(_In_ ID3D12CommandQueue* commandQueue); + + void ReleaseDevice(); + void RestoreDevice(_In_ ID3D12Device* context, + const DirectX::RenderTargetState& renderTarget, + DirectX::ResourceUploadBatch& resourceUpload, + DirectX::DescriptorPile& pile); + + void SetUser(_In_opt_ XUserHandle user, _In_ XTaskQueueHandle queue); + + void AddLog(const std::string& str); + void ClearLog(); + void Render(_In_ ID3D12GraphicsCommandList *commandList); + + void SetViewport(const D3D12_VIEWPORT &viewport); + + void SetWindowSize(float width, float height) { m_scaleWidth = width / LAYOUT_PIXEL_WIDTH, m_scaleHeight = height / LAYOUT_PIXEL_HEIGHT; } + + private: + SampleLiveInfoHUD(SampleLiveInfoHUD&&) = delete; + SampleLiveInfoHUD& operator= (SampleLiveInfoHUD&&) = delete; + + SampleLiveInfoHUD(SampleLiveInfoHUD const&) = delete; + SampleLiveInfoHUD& operator= (SampleLiveInfoHUD const&) = delete; + + private: + const float LAYOUT_PIXEL_WIDTH = 1920.f; + const float LAYOUT_PIXEL_HEIGHT = 1080.f; + + std::string m_sampleTitle; + std::string m_serviceConfigId; + std::string m_titleId; + std::string m_sandboxId; + std::string m_gamerTag; + Microsoft::WRL::ComPtr m_gamerPic; + Microsoft::WRL::ComPtr m_gamerDefaultPic; + + Microsoft::WRL::ComPtr m_device; + + // Direct3D resources + std::unique_ptr m_batch; + std::unique_ptr m_smallFont; + std::unique_ptr m_boldFont; + std::unique_ptr m_titleFont; + + float m_scaleWidth; + float m_scaleHeight; + + D3D12_CPU_DESCRIPTOR_HANDLE m_gamerPicCPU; + D3D12_GPU_DESCRIPTOR_HANDLE m_gamerPicGPU; + std::unique_ptr m_gamerPicData; + size_t m_gamerPicDataSize; + std::atomic m_gamerPicReady; + + std::mutex m_logLinesMutex; + std::vector m_logLines; + }; +} diff --git a/Tests/GDK/ManualTest.GDK/StepTimer.h b/Tests/GDK/ManualTest.GDK/StepTimer.h new file mode 100644 index 00000000..bb15fffa --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/StepTimer.h @@ -0,0 +1,190 @@ +// +// StepTimer.h - A simple timer that provides elapsed time information +// + +#pragma once + +#include +#include +#include + + +namespace DX +{ + // Helper class for animation and simulation timing. + class StepTimer + { + public: + StepTimer() noexcept(false) : + m_elapsedTicks(0), + m_totalTicks(0), + m_leftOverTicks(0), + m_frameCount(0), + m_framesPerSecond(0), + m_framesThisSecond(0), + m_qpcSecondCounter(0), + m_isFixedTimeStep(false), + m_targetElapsedTicks(TicksPerSecond / 60) + { + if (!QueryPerformanceFrequency(&m_qpcFrequency)) + { + throw std::exception(); + } + + if (!QueryPerformanceCounter(&m_qpcLastTime)) + { + throw std::exception(); + } + + // Initialize max delta to 1/10 of a second. + m_qpcMaxDelta = static_cast(m_qpcFrequency.QuadPart / 10); + } + + // Get elapsed time since the previous Update call. + uint64_t GetElapsedTicks() const noexcept { return m_elapsedTicks; } + double GetElapsedSeconds() const noexcept { return TicksToSeconds(m_elapsedTicks); } + + // Get total time since the start of the program. + uint64_t GetTotalTicks() const noexcept { return m_totalTicks; } + double GetTotalSeconds() const noexcept { return TicksToSeconds(m_totalTicks); } + + // Get total number of updates since start of the program. + uint32_t GetFrameCount() const noexcept { return m_frameCount; } + + // Get the current framerate. + uint32_t GetFramesPerSecond() const noexcept { return m_framesPerSecond; } + + // Set whether to use fixed or variable timestep mode. + void SetFixedTimeStep(bool isFixedTimestep) noexcept { m_isFixedTimeStep = isFixedTimestep; } + + // Set how often to call Update when in fixed timestep mode. + void SetTargetElapsedTicks(uint64_t targetElapsed) noexcept { m_targetElapsedTicks = targetElapsed; } + void SetTargetElapsedSeconds(double targetElapsed) noexcept { m_targetElapsedTicks = SecondsToTicks(targetElapsed); } + + // Integer format represents time using 10,000,000 ticks per second. + static constexpr uint64_t TicksPerSecond = 10000000; + + static constexpr double TicksToSeconds(uint64_t ticks) noexcept { return static_cast(ticks) / TicksPerSecond; } + static constexpr uint64_t SecondsToTicks(double seconds) noexcept { return static_cast(seconds * TicksPerSecond); } + + // After an intentional timing discontinuity (for instance a blocking IO operation) + // call this to avoid having the fixed timestep logic attempt a set of catch-up + // Update calls. + + void ResetElapsedTime() + { + if (!QueryPerformanceCounter(&m_qpcLastTime)) + { + throw std::exception(); + } + + m_leftOverTicks = 0; + m_framesPerSecond = 0; + m_framesThisSecond = 0; + m_qpcSecondCounter = 0; + } + + // Update timer state, calling the specified Update function the appropriate number of times. + template + void Tick(const TUpdate& update) + { + // Query the current time. + LARGE_INTEGER currentTime; + + if (!QueryPerformanceCounter(¤tTime)) + { + throw std::exception(); + } + + uint64_t timeDelta = static_cast(currentTime.QuadPart - m_qpcLastTime.QuadPart); + + m_qpcLastTime = currentTime; + m_qpcSecondCounter += timeDelta; + + // Clamp excessively large time deltas (e.g. after paused in the debugger). + if (timeDelta > m_qpcMaxDelta) + { + timeDelta = m_qpcMaxDelta; + } + + // Convert QPC units into a canonical tick format. This cannot overflow due to the previous clamp. + timeDelta *= TicksPerSecond; + timeDelta /= static_cast(m_qpcFrequency.QuadPart); + + uint32_t lastFrameCount = m_frameCount; + + if (m_isFixedTimeStep) + { + // Fixed timestep update logic + + // If the app is running very close to the target elapsed time (within 1/4 of a millisecond) just clamp + // the clock to exactly match the target value. This prevents tiny and irrelevant errors + // from accumulating over time. Without this clamping, a game that requested a 60 fps + // fixed update, running with vsync enabled on a 59.94 NTSC display, would eventually + // accumulate enough tiny errors that it would drop a frame. It is better to just round + // small deviations down to zero to leave things running smoothly. + + if (static_cast(std::abs(static_cast(timeDelta - m_targetElapsedTicks))) < TicksPerSecond / 4000) + { + timeDelta = m_targetElapsedTicks; + } + + m_leftOverTicks += timeDelta; + + while (m_leftOverTicks >= m_targetElapsedTicks) + { + m_elapsedTicks = m_targetElapsedTicks; + m_totalTicks += m_targetElapsedTicks; + m_leftOverTicks -= m_targetElapsedTicks; + m_frameCount++; + + update(); + } + } + else + { + // Variable timestep update logic. + m_elapsedTicks = timeDelta; + m_totalTicks += timeDelta; + m_leftOverTicks = 0; + m_frameCount++; + + update(); + } + + // Track the current framerate. + if (m_frameCount != lastFrameCount) + { + m_framesThisSecond++; + } + + if (m_qpcSecondCounter >= static_cast(m_qpcFrequency.QuadPart)) + { + m_framesPerSecond = m_framesThisSecond; + m_framesThisSecond = 0; + m_qpcSecondCounter %= static_cast(m_qpcFrequency.QuadPart); + } + } + + private: + // Source timing data uses QPC units. + LARGE_INTEGER m_qpcFrequency; + LARGE_INTEGER m_qpcLastTime; + uint64_t m_qpcMaxDelta; + + // Derived timing data uses a canonical tick format. + uint64_t m_elapsedTicks; + uint64_t m_totalTicks; + uint64_t m_leftOverTicks; + + // Members for tracking the framerate. + uint32_t m_frameCount; + uint32_t m_framesPerSecond; + uint32_t m_framesThisSecond; + uint64_t m_qpcSecondCounter; + + // Members for configuring fixed timestep mode. + bool m_isFixedTimeStep; + uint64_t m_targetElapsedTicks; + }; +} diff --git a/Tests/GDK/ManualTest.GDK/pch.cpp b/Tests/GDK/ManualTest.GDK/pch.cpp new file mode 100644 index 00000000..fb58e703 --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/pch.cpp @@ -0,0 +1,10 @@ +//-------------------------------------------------------------------------------------- +// pch.cpp +// +// Include the standard header and generate the precompiled header. +// +// Advanced Technology Group (ATG) +// Copyright (C) Microsoft Corporation. All rights reserved. +//-------------------------------------------------------------------------------------- + +#include "pch.h" diff --git a/Tests/GDK/ManualTest.GDK/pch.h b/Tests/GDK/ManualTest.GDK/pch.h new file mode 100644 index 00000000..089c5a00 --- /dev/null +++ b/Tests/GDK/ManualTest.GDK/pch.h @@ -0,0 +1,153 @@ +//-------------------------------------------------------------------------------------- +// pch.h +// +// Header for standard system include files. +// +// Advanced Technology Group (ATG) +// Copyright (C) Microsoft Corporation. All rights reserved. +//-------------------------------------------------------------------------------------- + +#pragma once + +#include +#define _WIN32_WINNT 0x0A00 +#include + +// Use the C++ standard templated min/max +#define NOMINMAX + +// DirectX apps don't need GDI +#define NODRAWTEXT +#define NOGDI +#define NOBITMAP + +// Include if you need this +#define NOMCX + +// Include if you need this +#define NOSERVICE + +// WinHelp is deprecated +#define NOHELP + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include + +#include +#include + +#include + +#if _GRDK_VER < 0x55F00C58 /* GDK Edition 220300 */ +#error This sample requires the March 2022 GDK or later +#endif + +#ifdef _GAMING_XBOX_SCARLETT +#include +#include +#elif defined(_GAMING_XBOX) +#include +#include +#else +#include +#include + +#ifdef _DEBUG +#include +#endif + +#include "d3dx12.h" +#endif + +#define _XM_NO_XMVECTOR_OVERLOADS_ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _GAMING_XBOX +#include +#else +// To use graphics markup events with the latest version of PIX, change this to include +// then add the NuGet package WinPixEventRuntime to the project. +#include +#endif +#include "xal\xal.h" +#include "xsapi-c\services_c.h" + +#include +#include +#include +#include + +#include "DescriptorHeap.h" +#include "ResourceUploadBatch.h" +#include "SpriteBatch.h" +#include "SpriteFont.h" + +#include "DirectXHelpers.h" +#include "GamePad.h" +#include "GraphicsMemory.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "RenderTargetState.h" + + + +namespace DX +{ + // Helper class for COM exceptions + class com_exception : public std::exception + { + public: + com_exception(HRESULT hr) noexcept : result(hr) {} + + const char* what() const override + { + static char s_str[64] = {}; + sprintf_s(s_str, "Failure with HRESULT of %08X", static_cast(result)); + return s_str; + } + + private: + HRESULT result; + }; + + // Helper utility converts D3D API failures into exceptions. + inline void ThrowIfFailed(HRESULT hr) + { + if (FAILED(hr)) + { +#ifdef _DEBUG + char str[64] = {}; + sprintf_s(str, "**ERROR** Fatal Error with HRESULT of %08X\n", static_cast(hr)); + OutputDebugStringA(str); + __debugbreak(); +#endif + throw com_exception(hr); + } + } +} + +// Enable off by default warnings to improve code conformance +#pragma warning(default : 4061 4062 4191 4242 4263 4264 4265 4266 4289 4365 4746 4826 4841 4986 4987 5029 5038 5042) diff --git a/Tests/UnitTests/Mocks/http_mock.cpp b/Tests/UnitTests/Mocks/http_mock.cpp index 0618a756..302a25ba 100644 --- a/Tests/UnitTests/Mocks/http_mock.cpp +++ b/Tests/UnitTests/Mocks/http_mock.cpp @@ -55,6 +55,7 @@ HttpMock & HttpMock::operator=(HttpMock&& other) HttpMock::~HttpMock() { HCMockRemoveMock(m_handle); + HCMockCallCloseHandle(m_handle); } void HttpMock::SetResponseHttpStatus(uint32_t httpStatus) const noexcept