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