diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index 200369e64..dd7b03074 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -117,9 +117,6 @@ bool GraphicsService::Init() // Platform default if (!device) { -#if PLATFORM_WINDOWS - - // Windows #if GRAPHICS_API_DIRECTX11 if (!device) device = CreateGPUDeviceDX11(); @@ -132,65 +129,9 @@ bool GraphicsService::Init() if (!device) device = CreateGPUDeviceVulkan(); #endif - -#elif PLATFORM_UWP - - // UWP - if (!device) - device = CreateGPUDeviceDX11(); - -#elif PLATFORM_LINUX - - // Linux -#if GRAPHICS_API_VULKAN - if (!device) - device = CreateGPUDeviceVulkan(); -#endif - -#elif PLATFORM_PS4 - - // PS4 #if GRAPHICS_API_PS4 if (!device) device = CreateGPUDevicePS4(); -#endif - -#elif PLATFORM_XBOX_ONE - - // Xbox One -#if GRAPHICS_API_DIRECTX12 - if (!device) - device = CreateGPUDeviceDX12(); -#endif - -#elif PLATFORM_XBOX_SCARLETT - - // Xbox Scarlett -#if GRAPHICS_API_DIRECTX12 - if (!device) - device = CreateGPUDeviceDX12(); -#endif - -#elif PLATFORM_ANDROID - - // Android -#if GRAPHICS_API_VULKAN - if (!device) - device = CreateGPUDeviceVulkan(); -#endif - -#elif PLATFORM_SWITCH - - // Switch -#if GRAPHICS_API_VULKAN - if (!device) - device = CreateGPUDeviceVulkan(); -#endif - -#elif !defined(GRAPHICS_API_NULL) - -#error "Platform does not support GPU devices." - #endif } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index 40da7c1e4..32d1b7238 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -198,20 +198,6 @@ GPUDeviceDX12::GPUDeviceDX12(IDXGIFactory4* dxgiFactory, GPUAdapterDX* adapter) { } -#if PLATFORM_XBOX_SCARLETT -namespace XboxScarlett -{ - extern Action OnSuspend; - extern Action OnResume; -} -#elif PLATFORM_XBOX_ONE -namespace XboxOne -{ - extern Action OnSuspend; - extern Action OnResume; -} -#endif - bool GPUDeviceDX12::Init() { #if PLATFORM_XBOX_SCARLETT || PLATFORM_XBOX_ONE @@ -264,12 +250,9 @@ bool GPUDeviceDX12::Init() LOG(Info, "Hardware Version: {0}", hwVer); updateFrameEvents(); -#if PLATFORM_XBOX_SCARLETT - XboxScarlett::OnSuspend.Bind(this); - XboxScarlett::OnResume.Bind(this); -#elif PLATFORM_XBOX_ONE - XboxOne::OnSuspend.Bind(this); - XboxOne::OnResume.Bind(this); +#if PLATFORM_GDK + GDKPlatform::OnSuspend.Bind(this); + GDKPlatform::OnResume.Bind(this); #endif #else // Get DXGI adapter diff --git a/Source/Engine/Platform/GDK/GDKInput.cpp b/Source/Engine/Platform/GDK/GDKInput.cpp new file mode 100644 index 000000000..b0cbfbbe6 --- /dev/null +++ b/Source/Engine/Platform/GDK/GDKInput.cpp @@ -0,0 +1,253 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#if PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT + +#include "GDKInput.h" +#include "Engine/Input/Input.h" +#include "Engine/Input/Gamepad.h" +#include "Engine/Platform/Win32/IncludeWindowsHeaders.h" +#include + +static_assert(sizeof(Guid) <= sizeof(APP_LOCAL_DEVICE_ID), "Invalid Game Input deviceId size."); + +String ToString(GameInputString const* name) +{ + return name && name->data ? String(name->data) : String::Empty; +} + +/// +/// Implementation of the gamepad device for GDK platform. +/// +/// +class GDKGamepad : public Gamepad +{ +public: + + /// + /// Initializes a new instance of the class. + /// + /// The input device. + explicit GDKGamepad(IGameInputDevice* device) + : Gamepad(*(Guid*)&device->GetDeviceInfo()->deviceId, ::ToString(device->GetDeviceInfo()->displayName)) + , Device(device) + { + Device->AddRef(); + } + + /// + /// Finalizes an instance of the class. + /// + ~GDKGamepad() + { + Device->Release(); + } + + /// + /// The device. + /// + IGameInputDevice* Device; + +public: + + // [Gamepad] + void SetVibration(const GamepadVibrationState& state) override; + +protected: + + // [Gamepad] + bool UpdateState() override; +}; + +namespace GDKInputImpl +{ + IGameInput* GameInput = nullptr; + IGameInputReading* PrevReading = nullptr; +} + +using namespace GDKInputImpl; + +bool IsSameDevice(const APP_LOCAL_DEVICE_ID& first, const APP_LOCAL_DEVICE_ID& second) +{ + return memcmp(&first, &second, APP_LOCAL_DEVICE_ID_SIZE) == 0; +} + +bool IsSameDevice(IGameInputDevice* first, IGameInputDevice* second) +{ + return IsSameDevice(first->GetDeviceInfo()->deviceId, second->GetDeviceInfo()->deviceId); +} + +void TryAddGamepad(IGameInputDevice* device) +{ + for (auto& gamepad : Input::Gamepads) + { + if (IsSameDevice(((GDKGamepad*)gamepad)->Device, device)) + return; + } + + Input::Gamepads.Add(New(device)); + Input::OnGamepadsChanged(); +} + +void CALLBACK OnDeviceEnumerated( + _In_ GameInputCallbackToken callbackToken, + _In_ void* context, + _In_ IGameInputDevice* device, + _In_ uint64_t timestamp, + _In_ GameInputDeviceStatus currentStatus, + _In_ GameInputDeviceStatus previousStatus) +{ + TryAddGamepad(device); +} + +void GDKInput::Init() +{ + GameInputCreate(&GameInput); + + // Find connected devices + GameInputCallbackToken token; + if (SUCCEEDED(GameInput->RegisterDeviceCallback( + nullptr, + GameInputKindGamepad, + GameInputDeviceAnyStatus, + GameInputBlockingEnumeration, + nullptr, + OnDeviceEnumerated, + &token))) + { + GameInput->UnregisterCallback(token, 5000); + } +} + +void GDKInput::Exit() +{ + if (PrevReading) + { + PrevReading->Release(); + PrevReading = nullptr; + } + if (GameInput) + { + GameInput->Release(); + GameInput = nullptr; + } +} + +void GDKInput::Update() +{ + while (true) + { + // Read input + IGameInputReading* reading = nullptr; + if (!PrevReading) + { + if (FAILED(GDKInputImpl::GameInput->GetCurrentReading(GameInputKindGamepad, nullptr, &reading))) + { + break; + } + PrevReading = reading; + } + else + { + const HRESULT hr = GameInput->GetNextReading(PrevReading, GameInputKindGamepad, nullptr, &reading); + if (SUCCEEDED(hr)) + { + PrevReading->Release(); + PrevReading = reading; + } + else if (hr != GAMEINPUT_E_READING_NOT_FOUND) + { + PrevReading->Release(); + PrevReading = nullptr; + break; + } + } + if (!reading) + break; + + // Check if new device was connected + IGameInputDevice* device; + reading->GetDevice(&device); + TryAddGamepad(device); + device->Release(); + } +} + +void GDKGamepad::SetVibration(const GamepadVibrationState& state) +{ + GameInputRumbleParams vibration; + vibration.lowFrequency = state.LeftSmall; + vibration.highFrequency = state.RightSmall; + vibration.leftTrigger = state.LeftLarge; + vibration.rightTrigger = state.RightLarge; + Device->SetRumbleState(&vibration); +} + +bool GDKGamepad::UpdateState() +{ + // Gather device state + IGameInputReading* reading = nullptr; + if (FAILED(GDKInputImpl::GameInput->GetCurrentReading(GameInputKindGamepad,Device, &reading))) + { + return true; + } + GameInputGamepadState state; + if (!reading->GetGamepadState(&state)) + { + return true; + } + const float deadZone = 0.2f; + + // Process buttons state + _state.Buttons[(int32)GamepadButton::A] = state.buttons & GameInputGamepadA; + _state.Buttons[(int32)GamepadButton::B] = state.buttons & GameInputGamepadB; + _state.Buttons[(int32)GamepadButton::X] = state.buttons & GameInputGamepadX; + _state.Buttons[(int32)GamepadButton::Y] = state.buttons & GameInputGamepadY; + _state.Buttons[(int32)GamepadButton::LeftShoulder] = state.buttons & GameInputGamepadLeftShoulder; + _state.Buttons[(int32)GamepadButton::RightShoulder] = state.buttons & GameInputGamepadRightShoulder; + _state.Buttons[(int32)GamepadButton::Back] = state.buttons & GameInputGamepadMenu; + _state.Buttons[(int32)GamepadButton::Start] = state.buttons & GameInputGamepadView; + _state.Buttons[(int32)GamepadButton::LeftThumb] = state.buttons & GameInputGamepadLeftThumbstick; + _state.Buttons[(int32)GamepadButton::RightThumb] = state.buttons & GameInputGamepadRightThumbstick; + _state.Buttons[(int32)GamepadButton::LeftTrigger] = state.leftTrigger > deadZone; + _state.Buttons[(int32)GamepadButton::RightTrigger] = state.rightTrigger > deadZone; + _state.Buttons[(int32)GamepadButton::DPadUp] = state.buttons & GameInputGamepadDPadUp; + _state.Buttons[(int32)GamepadButton::DPadDown] = state.buttons & GameInputGamepadDPadDown; + _state.Buttons[(int32)GamepadButton::DPadLeft] = state.buttons & GameInputGamepadDPadLeft; + _state.Buttons[(int32)GamepadButton::DPadRight] = state.buttons & GameInputGamepadDPadRight; + _state.Buttons[(int32)GamepadButton::LeftStickUp] = state.leftThumbstickY > deadZone; + _state.Buttons[(int32)GamepadButton::LeftStickDown] = state.leftThumbstickY < -deadZone; + _state.Buttons[(int32)GamepadButton::LeftStickLeft] = state.leftThumbstickX < -deadZone; + _state.Buttons[(int32)GamepadButton::LeftStickRight] = state.leftThumbstickX > deadZone; + _state.Buttons[(int32)GamepadButton::RightStickUp] = state.rightThumbstickY > deadZone; + _state.Buttons[(int32)GamepadButton::RightStickDown] = state.rightThumbstickY < -deadZone; + _state.Buttons[(int32)GamepadButton::RightStickLeft] = state.rightThumbstickX < -deadZone; + _state.Buttons[(int32)GamepadButton::RightStickRight] = state.rightThumbstickX > deadZone; + + // Process axes state + _state.Axis[(int32)GamepadAxis::LeftStickX] = state.leftThumbstickX; + _state.Axis[(int32)GamepadAxis::LeftStickY] = state.leftThumbstickY; + _state.Axis[(int32)GamepadAxis::RightStickX] = state.rightThumbstickX; + _state.Axis[(int32)GamepadAxis::RightStickY] = state.rightThumbstickY; + _state.Axis[(int32)GamepadAxis::LeftTrigger] = state.leftTrigger; + _state.Axis[(int32)GamepadAxis::RightTrigger] = state.rightTrigger; + + reading->Release(); + + return false; +} + +#else + +void GDKInput::Init() +{ +} + +void GDKInput::Exit() +{ +} + +void GDKInput::Update() +{ +} + +#endif diff --git a/Source/Engine/Platform/GDK/GDKInput.h b/Source/Engine/Platform/GDK/GDKInput.h new file mode 100644 index 000000000..c4e71b912 --- /dev/null +++ b/Source/Engine/Platform/GDK/GDKInput.h @@ -0,0 +1,19 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_GDK + +/// +/// GDK platform specific implementation of the input system parts. +/// +class GDKInput +{ +public: + + static void Init(); + static void Exit(); + static void Update(); +}; + +#endif diff --git a/Source/Engine/Platform/GDK/GDKPlatform.cpp b/Source/Engine/Platform/GDK/GDKPlatform.cpp new file mode 100644 index 000000000..9ceb40950 --- /dev/null +++ b/Source/Engine/Platform/GDK/GDKPlatform.cpp @@ -0,0 +1,631 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#if PLATFORM_GDK + +#include "Engine/Platform/Platform.h" +#include "Engine/Platform/Window.h" +#include "Engine/Platform/CreateWindowSettings.h" +#include "Engine/Platform/WindowsManager.h" +#include "Engine/Platform/MemoryStats.h" +#include "Engine/Platform/BatteryInfo.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Platform/MessageBox.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Utilities/StringConverter.h" +#include "Engine/Platform/Win32/IncludeWindowsHeaders.h" +#include "GDKInput.h" +#include +#include + +inline bool operator==(const APP_LOCAL_DEVICE_ID& l, const APP_LOCAL_DEVICE_ID& r) +{ + return Platform::MemoryCompare(&l, &r, sizeof(APP_LOCAL_DEVICE_ID)) == 0; +} + +const Char* GDKPlatform::ApplicationWindowClass = TEXT("FlaxWindow"); +void* GDKPlatform::Instance = nullptr; +Delegate<> GDKPlatform::OnSuspend; +Delegate<> GDKPlatform::OnResume; + +struct User +{ + XUserHandle UserHandle; + XUserLocalId LocalId; + Array> AssociatedDevices; + + void Set(XUserHandle userHandle, XUserLocalId localId) + { + UserHandle = userHandle; + LocalId = localId; + AssociatedDevices.Clear(); + } + + void Unset() + { + XUserCloseHandle(UserHandle); + AssociatedDevices.Clear(); + } +}; + +namespace +{ + bool IsSuspended = false; + HANDLE PlmSuspendComplete = nullptr; + HANDLE PlmSignalResume = nullptr; + PAPPSTATE_REGISTRATION Plm = {}; + String UserLocale, ComputerName; + XTaskQueueHandle TaskQueue = nullptr; + Array> Users; + XTaskQueueRegistrationToken UserChangeEventCallbackToken; + XTaskQueueRegistrationToken UserDeviceAssociationChangedCallbackToken; +} + +LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_USER: + { + LOG(Info, "Suspending application"); + IsSuspended = true; + GDKPlatform::OnSuspend(); + + // Complete deferral + SetEvent(PlmSuspendComplete); + + (void)WaitForSingleObject(PlmSignalResume, INFINITE); + + IsSuspended = false; + LOG(Info, "Resuming application"); + GDKPlatform::OnResume(); + return DefWindowProc(hWnd, msg, wParam, lParam); + } + } + + // Find window to process that message + if (hWnd != nullptr) + { + // Find window by handle + const auto win = WindowsManager::GetByNativePtr(hWnd); + if (win) + { + return static_cast(win)->WndProc(msg, wParam, lParam); + } + } + + // Default + return DefWindowProc(hWnd, msg, wParam, lParam); +} + +void CALLBACK UserChangeEventCallback(_In_opt_ void* context,_In_ XUserLocalId userLocalId, _In_ XUserChangeEvent event) +{ + LOG(Info, "User event (userLocalId: {0}, event: {1})", userLocalId.value, (int32)event); + + switch (event) + { + case XUserChangeEvent::SignedInAgain: + break; + case XUserChangeEvent::SignedOut: + for (int32 i = 0; i < Users.Count(); i++) + { + if (Users[i].LocalId.value == userLocalId.value) + { + Users[i].Unset(); + Users.RemoveAt(i); + break; + } + } + break; + default: ; + } +} + +String ToString(const APP_LOCAL_DEVICE_ID& deviceId) +{ + return String::Format(TEXT("{}-{}-{}-{}-{}-{}-{}-{}"), + *reinterpret_cast(&deviceId.value[0]), + *reinterpret_cast(&deviceId.value[4]), + *reinterpret_cast(&deviceId.value[8]), + *reinterpret_cast(&deviceId.value[12]), + *reinterpret_cast(&deviceId.value[16]), + *reinterpret_cast(&deviceId.value[20]), + *reinterpret_cast(&deviceId.value[24]), + *reinterpret_cast(&deviceId.value[28])); +} + +User* FindUser(const XUserLocalId& id) +{ + User* result = nullptr; + for (auto& user : Users) + { + if (user.LocalId.value == id.value) + { + result = &user; + break; + } + } + return result; +} + +void CALLBACK UserDeviceAssociationChangedCallback(_In_opt_ void* context,_In_ const XUserDeviceAssociationChange* change) +{ + LOG(Info, "User device association event (deviceId: {0}, oldUser: {1}, newUser: {2})", ToString(change->deviceId), change->oldUser.value, change->newUser.value); + + User* oldGameUser = FindUser(change->oldUser); + if (oldGameUser) + { + oldGameUser->AssociatedDevices.Remove(change->deviceId); + } + + User* newGameUser = FindUser(change->newUser); + if (newGameUser) + { + newGameUser->AssociatedDevices.Add(change->deviceId); + } +} + +void OnMainWindowCreated(HWND hWnd) +{ + // Register for app suspending/resuming events + PlmSuspendComplete = CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE); + PlmSignalResume = CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE); + if (!PlmSuspendComplete || !PlmSignalResume) + return; + if (RegisterAppStateChangeNotification([](BOOLEAN quiesced, PVOID context) + { + if (quiesced) + { + ResetEvent(PlmSuspendComplete); + ResetEvent(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 + (void)WaitForSingleObject(PlmSuspendComplete, INFINITE); + } + else + { + SetEvent(PlmSignalResume); + } + }, hWnd, &Plm)) + return; +} + +void CALLBACK AddUserComplete(_In_ XAsyncBlock* ab) +{ + XUserHandle userHandle; + HRESULT hr = XUserAddResult(ab, &userHandle); + if (SUCCEEDED(hr)) + { + XUserLocalId userLocalId; + XUserGetLocalId(userHandle, &userLocalId); + + XUserLocalId localId; + XUserGetLocalId(userHandle, &localId); + + if (FindUser(localId) == nullptr) + { + // Add user + auto& user = Users.AddOne(); + user.Set(userHandle, userLocalId); + } + } + + delete ab; +} + +DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon) +{ + const char *firstButtonText, *secondButtonText, *thirdButtonText; + XGameUiMessageDialogButton defaultButton, cancelButton = XGameUiMessageDialogButton::First; + switch (buttons) + { + case MessageBoxButtons::AbortRetryIgnore: + firstButtonText = "Abort"; + secondButtonText = "Retry"; + thirdButtonText = "Ignore"; + defaultButton = XGameUiMessageDialogButton::Second; + cancelButton = XGameUiMessageDialogButton::Third; + break; + case MessageBoxButtons::OK: + firstButtonText = "OK"; + secondButtonText = nullptr; + thirdButtonText = nullptr; + defaultButton = XGameUiMessageDialogButton::First; + cancelButton = XGameUiMessageDialogButton::First; + break; + case MessageBoxButtons::OKCancel: + firstButtonText = "OK"; + secondButtonText = "Cancel"; + thirdButtonText = nullptr; + defaultButton = XGameUiMessageDialogButton::First; + cancelButton = XGameUiMessageDialogButton::Second; + break; + case MessageBoxButtons::RetryCancel: + firstButtonText = "Retry"; + secondButtonText = "Cancel"; + thirdButtonText = nullptr; + defaultButton = XGameUiMessageDialogButton::First; + cancelButton = XGameUiMessageDialogButton::Second; + break; + case MessageBoxButtons::YesNo: + firstButtonText = "Yes"; + secondButtonText = "No"; + thirdButtonText = nullptr; + defaultButton = XGameUiMessageDialogButton::First; + cancelButton = XGameUiMessageDialogButton::Second; + break; + case MessageBoxButtons::YesNoCancel: + firstButtonText = "Yes"; + secondButtonText = "No"; + thirdButtonText = "Cancel"; + defaultButton = XGameUiMessageDialogButton::First; + cancelButton = XGameUiMessageDialogButton::Third; + break; + default: + return DialogResult::None; + } + const StringAsANSI<> textAnsi(text.Get(), text.Length()); + const StringAsANSI<> captionAnsi(caption.Get(), caption.Length()); + + // Show dialog and wait for the result + DialogResult result = DialogResult::None; + XTaskQueueHandle queue; + if (FAILED(XTaskQueueCreate(XTaskQueueDispatchMode::ThreadPool, XTaskQueueDispatchMode::Immediate, &queue))) + { + return DialogResult::None; + } + XAsyncBlock* ab = new XAsyncBlock(); + Platform::MemoryClear(ab, sizeof(XAsyncBlock)); + ab->queue = queue; + XGameUiMessageDialogButton button; + if (SUCCEEDED(XGameUiShowMessageDialogAsync(ab, captionAnsi.Get(), textAnsi.Get(), firstButtonText, secondButtonText, thirdButtonText, defaultButton, cancelButton)) && + SUCCEEDED(XAsyncGetStatus(ab, true)) && + SUCCEEDED(XGameUiShowMessageDialogResult(ab, &button))) + { + switch (buttons) + { + case MessageBoxButtons::AbortRetryIgnore: + result = button == XGameUiMessageDialogButton::First ? DialogResult::Abort : button == XGameUiMessageDialogButton::Second ? DialogResult::Retry : DialogResult::Ignore; + break; + case MessageBoxButtons::OK: + result = DialogResult::OK; + break; + case MessageBoxButtons::OKCancel: + result = button == XGameUiMessageDialogButton::First ? DialogResult::OK : DialogResult::Cancel; + break; + case MessageBoxButtons::RetryCancel: + result = button == XGameUiMessageDialogButton::First ? DialogResult::Retry : DialogResult::Cancel; + break; + case MessageBoxButtons::YesNo: + result = button == XGameUiMessageDialogButton::First ? DialogResult::Yes : DialogResult::No; + break; + case MessageBoxButtons::YesNoCancel: + result = button == XGameUiMessageDialogButton::First ? DialogResult::Yes : button == XGameUiMessageDialogButton::Second ? DialogResult::No : DialogResult::Cancel; + break; + } + } + + // Cleanup + XTaskQueueTerminate(queue, true, nullptr, nullptr); + delete ab; + + return result; +} + +void GDKPlatform::PreInit(void* hInstance) +{ + ASSERT(hInstance); + Instance = hInstance; + + // Initialize the Game Runtime APIs + if (FAILED(XGameRuntimeInitialize())) + { + Error(TEXT("Game runtime initialization failed!")); + exit(-1); + } + + // Register window class + WNDCLASSW windowsClass; + Platform::MemoryClear(&windowsClass, sizeof(WNDCLASS)); + windowsClass.style = CS_HREDRAW | CS_VREDRAW; + windowsClass.lpfnWndProc = WndProc; + windowsClass.hInstance = (HINSTANCE)Instance; + windowsClass.lpszClassName = ApplicationWindowClass; + if (!RegisterClassW(&windowsClass)) + { + Error(TEXT("Window class registration failed!")); + exit(-1); + } +} + +bool GDKPlatform::IsRunningOnDevKit() +{ + const XSystemDeviceType deviceType = XSystemGetDeviceType(); + return deviceType == XSystemDeviceType::XboxOneXDevkit || deviceType == XSystemDeviceType::XboxScarlettDevkit; +} + +bool GDKPlatform::Init() +{ + if (Win32Platform::Init()) + return true; + + DWORD tmp; + Char buffer[256]; + + // Get user locale string + if (GetUserDefaultLocaleName(buffer, LOCALE_NAME_MAX_LENGTH)) + { + UserLocale = String(buffer); + } + + // Get computer name string + if (GetComputerNameW(buffer, &tmp)) + { + ComputerName = String(buffer); + } + + // Create a task queue that will process in the background on system threads and fire callbacks on a thread we choose in a serialized order + if (FAILED(XTaskQueueCreate(XTaskQueueDispatchMode::ThreadPool, XTaskQueueDispatchMode::Manual, &TaskQueue))) + return true; + + // Register for any change events for user + XUserRegisterForChangeEvent( + TaskQueue, + nullptr, + &UserChangeEventCallback, + &UserChangeEventCallbackToken + ); + + // Registers for any change to device association so that the application can keep up-to-date information about users and their associated devices + XUserRegisterForDeviceAssociationChanged( + TaskQueue, + nullptr, + &UserDeviceAssociationChangedCallback, + &UserDeviceAssociationChangedCallbackToken + ); + + // Login the default user + { + auto asyncBlock = new XAsyncBlock(); + asyncBlock->queue = TaskQueue; + asyncBlock->callback = AddUserComplete; + HRESULT hr = XUserAddAsync(XUserAddOptions::AddDefaultUserAllowingUI, asyncBlock); + if (FAILED(hr)) + delete asyncBlock; + } + + GDKInput::Init(); + + return false; +} + +void GDKPlatform::BeforeRun() +{ + // Log system info + const XSystemAnalyticsInfo analyticsInfo = XSystemGetAnalyticsInfo(); + LOG(Info, "{0}, {1}", StringAsUTF16<64>(analyticsInfo.family).Get(), StringAsUTF16<64>(analyticsInfo.form).Get()); + LOG(Info, "OS Version {0}.{1}.{2}.{3}", analyticsInfo.osVersion.major, analyticsInfo.osVersion.minor, analyticsInfo.osVersion.build, analyticsInfo.osVersion.revision); +} + +void GDKPlatform::Tick() +{ + PROFILE_CPU_NAMED("Application.Tick"); + + GDKInput::Update(); + + // Handle callbacks in the main thread to ensure thread safety + while (XTaskQueueDispatch(TaskQueue, XTaskQueuePort::Completion, 0)) + { + } + + // Check to see if any messages are waiting in the queue + MSG msg; + while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) + { + // Translate the message and dispatch it to WindowProc() + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +void GDKPlatform::BeforeExit() +{ +} + +void GDKPlatform::Exit() +{ + GDKInput::Exit(); + + XUserUnregisterForDeviceAssociationChanged(UserDeviceAssociationChangedCallbackToken, false); + XUserUnregisterForChangeEvent(UserChangeEventCallbackToken, false); + if (TaskQueue) + { + XTaskQueueCloseHandle(TaskQueue); + } + + UnregisterAppStateChangeNotification(Plm); + + CloseHandle(PlmSuspendComplete); + CloseHandle(PlmSignalResume); + + UnregisterClassW(ApplicationWindowClass, nullptr); + + XGameRuntimeUninitialize(); +} + +#if !BUILD_RELEASE + +void GDKPlatform::Log(const StringView& msg) +{ + OutputDebugStringW(msg.Get()); + OutputDebugStringW(TEXT(PLATFORM_LINE_TERMINATOR)); +} + +bool GDKPlatform::IsDebuggerPresent() +{ + return !!::IsDebuggerPresent(); +} + +#endif + +BatteryInfo GDKPlatform::GetBatteryInfo() +{ + BatteryInfo info; + info.State = BatteryInfo::States::Connected; + return info; +} + +int32 GDKPlatform::GetDpi() +{ + return 96; +} + +String GDKPlatform::GetUserLocaleName() +{ + return UserLocale; +} + +String GDKPlatform::GetComputerName() +{ + return ComputerName; +} + +String GDKPlatform::GetUserName() +{ + return String::Empty; +} + +bool GDKPlatform::GetHasFocus() +{ + return !IsSuspended; +} + +bool GDKPlatform::CanOpenUrl(const StringView& url) +{ + return Users.HasItems(); +} + +void GDKPlatform::OpenUrl(const StringView& url) +{ + const StringAsANSI<> urlANSI(url.Get(), url.Length()); + XLaunchUri(Users[0].UserHandle, urlANSI.Get()); +} + +struct GetMonitorBoundsData +{ + Vector2 Pos; + Rectangle Result; + + GetMonitorBoundsData(const Vector2& pos) + : Pos(pos) + , Result(Vector2::Zero, GDKPlatform::GetDesktopSize()) + { + } +}; + +Rectangle GDKPlatform::GetMonitorBounds(const Vector2& screenPos) +{ + return Rectangle(Vector2::Zero, GetDesktopSize()); +} + +Vector2 GDKPlatform::GetDesktopSize() +{ + return Vector2(1920, 1080); +} + +Rectangle GDKPlatform::GetVirtualDesktopBounds() +{ + return Rectangle(Vector2::Zero, GetDesktopSize()); +} + +void GDKPlatform::GetEnvironmentVariables(Dictionary& result) +{ + const LPWCH environmentStr = GetEnvironmentStringsW(); + if (environmentStr) + { + LPWCH env = environmentStr; + while (*env != '\0') + { + if (*env != '=') + { + WCHAR* str = wcschr(env, '='); + ASSERT(str); + result[String(env, (int32)(str - env))] = str + 1; + } + while (*env != '\0') + env++; + env++; + } + FreeEnvironmentStringsW(environmentStr); + } +} + +bool GDKPlatform::GetEnvironmentVariable(const String& name, String& value) +{ + const int32 bufferSize = 512; + Char buffer[bufferSize]; + DWORD result = GetEnvironmentVariableW(*name, buffer, bufferSize); + if (result == 0) + { + LOG_WIN32_LAST_ERROR; + return true; + } + if (bufferSize < result) + { + value.ReserveSpace(result); + result = GetEnvironmentVariableW(*name, *value, result); + if (!result) + { + LOG_WIN32_LAST_ERROR; + return FALSE; + } + } + else + { + value.Set(buffer, result); + } + return false; +} + +bool GDKPlatform::SetEnvironmentVariable(const String& name, const String& value) +{ + if (!SetEnvironmentVariableW(*name, *value)) + { + LOG_WIN32_LAST_ERROR; + return true; + } + return false; +} + +Window* GDKPlatform::CreateWindow(const CreateWindowSettings& settings) +{ + return New(settings); +} + +void* GDKPlatform::LoadLibrary(const Char* filename) +{ + ASSERT(filename); + void* handle = ::LoadLibraryW(filename); + if (!handle) + { + LOG(Warning, "Failed to load '{0}' (GetLastError={1})", filename, GetLastError()); + } + return handle; +} + +void GDKPlatform::FreeLibrary(void* handle) +{ + ::FreeLibrary((HMODULE)handle); +} + +void* GDKPlatform::GetProcAddress(void* handle, const char* symbol) +{ + return (void*)::GetProcAddress((HMODULE)handle, symbol); +} + +#endif diff --git a/Source/Engine/Platform/GDK/GDKPlatform.h b/Source/Engine/Platform/GDK/GDKPlatform.h new file mode 100644 index 000000000..87166fdbd --- /dev/null +++ b/Source/Engine/Platform/GDK/GDKPlatform.h @@ -0,0 +1,79 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_GDK + +#include "Engine/Platform/Win32/Win32Platform.h" + +/// +/// The GDK platform implementation and application management utilities. +/// +class GDKPlatform : public Win32Platform +{ +public: + + /// + /// Win32 application windows class name. + /// + static const Char* ApplicationWindowClass; + + /// + /// Handle to Win32 application instance. + /// + static void* Instance; + + static Delegate<> OnSuspend; + static Delegate<> OnResume; + +public: + + /// + /// Returns true if current OS version is Windows 10. + /// + static bool IsWindows10() + { + return true; + } + + /// + /// Pre initialize platform. + /// + /// The Win32 application instance. + static void PreInit(void* hInstance); + + static bool IsRunningOnDevKit(); + +public: + + // [Win32Platform] + static bool Init(); + static void BeforeRun(); + static void Tick(); + static void BeforeExit(); + static void Exit(); +#if !BUILD_RELEASE + static void Log(const StringView& msg); + static bool IsDebuggerPresent(); +#endif + static BatteryInfo GetBatteryInfo(); + static int32 GetDpi(); + static String GetUserLocaleName(); + static String GetComputerName(); + static String GetUserName(); + static bool GetHasFocus(); + static bool CanOpenUrl(const StringView& url); + static void OpenUrl(const StringView& url); + static Rectangle GetMonitorBounds(const Vector2& screenPos); + static Vector2 GetDesktopSize(); + static Rectangle GetVirtualDesktopBounds(); + static void GetEnvironmentVariables(Dictionary& result); + static bool GetEnvironmentVariable(const String& name, String& value); + static bool SetEnvironmentVariable(const String& name, const String& value); + static Window* CreateWindow(const CreateWindowSettings& settings); + static void* LoadLibrary(const Char* filename); + static void FreeLibrary(void* handle); + static void* GetProcAddress(void* handle, const char* symbol); +}; + +#endif diff --git a/Source/Engine/Platform/GDK/GDKWindow.cpp b/Source/Engine/Platform/GDK/GDKWindow.cpp new file mode 100644 index 000000000..136bd18f1 --- /dev/null +++ b/Source/Engine/Platform/GDK/GDKWindow.cpp @@ -0,0 +1,394 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#if PLATFORM_GDK + +#undef _GAMING_XBOX +#include "GDKWindow.h" +#include "GDKPlatform.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Math/Math.h" +#include "Engine/Graphics/GPUSwapChain.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Platform/Win32/IncludeWindowsHeaders.h" + +extern void OnMainWindowCreated(HWND hWnd); + +GDKWindow::GDKWindow(const CreateWindowSettings& settings) + : WindowBase(settings) +{ + int32 x = Math::TruncToInt(settings.Position.X); + int32 y = Math::TruncToInt(settings.Position.Y); + int32 clientWidth = Math::TruncToInt(settings.Size.X); + int32 clientHeight = Math::TruncToInt(settings.Size.Y); + int32 windowWidth = clientWidth; + int32 windowHeight = clientHeight; + _clientSize = Vector2((float)clientWidth, (float)clientHeight); + + // Setup window style + uint32 style = WS_POPUP, exStyle = 0; + if (settings.SupportsTransparency) + exStyle |= WS_EX_LAYERED; + if (!settings.ActivateWhenFirstShown) + exStyle |= WS_EX_NOACTIVATE; + if (settings.ShowInTaskbar) + exStyle |= WS_EX_APPWINDOW; + else + exStyle |= WS_EX_TOOLWINDOW; + if (settings.IsTopmost) + exStyle |= WS_EX_TOPMOST; + if (!settings.AllowInput) + exStyle |= WS_EX_TRANSPARENT; + if (settings.AllowMaximize) + style |= WS_MAXIMIZEBOX; + if (settings.AllowMinimize) + style |= WS_MINIMIZEBOX; + if (settings.HasSizingFrame) + style |= WS_THICKFRAME; + + // Check if window should have a border + if (settings.HasBorder) + { + // Create window style flags + style |= WS_OVERLAPPED | WS_SYSMENU | WS_BORDER | WS_CAPTION; + exStyle |= 0; + + // Adjust window size and positions to take into account window border + RECT winRect = { 0, 0, clientWidth, clientHeight }; + AdjustWindowRectEx(&winRect, style, FALSE, exStyle); + x += winRect.left; + y += winRect.top; + windowWidth = winRect.right - winRect.left; + windowHeight = winRect.bottom - winRect.top; + } + else + { + // Create window style flags + style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + exStyle |= WS_EX_WINDOWEDGE; + } + + // Creating the window + _handle = CreateWindowExW( + exStyle, + Platform::ApplicationWindowClass, + settings.Title.GetText(), + style, + x, + y, + windowWidth, + windowHeight, + settings.Parent ? static_cast(settings.Parent->GetNativePtr()) : nullptr, + nullptr, + (HINSTANCE)Platform::Instance, + nullptr); + + // Validate result + if (!HasHWND()) + { + LOG_WIN32_LAST_ERROR; + Platform::Fatal(TEXT("Cannot create window.")); + } + + OnMainWindowCreated(_handle); +} + +GDKWindow::~GDKWindow() +{ + if (HasHWND()) + { + // Destroy window + if (DestroyWindow(_handle) == 0) + { + LOG(Warning, "DestroyWindow failed! Error: {0:#x}", GetLastError()); + } + + // Clear + _handle = nullptr; + _visible = false; + } +} + +void* GDKWindow::GetNativePtr() const +{ + return _handle; +} + +void GDKWindow::Show() +{ + if (!_visible) + { + InitSwapChain(); + if (_showAfterFirstPaint) + { + if (RenderTask) + RenderTask->Enabled = true; + return; + } + + ASSERT(HasHWND()); + + // Show + ShowWindow(_handle, (_settings.AllowInput && _settings.ActivateWhenFirstShown) ? SW_SHOW : SW_SHOWNA); + + // Base + WindowBase::Show(); + + _focused = true; // TODO: remove it and check if the initial focus works when game starts with rendering, do we get WM_ACTIVATEAPP? + } +} + +void GDKWindow::Hide() +{ + if (_visible) + { + ASSERT(HasHWND()); + + // Hide + ShowWindow(_handle, SW_HIDE); + + // Base + WindowBase::Hide(); + } +} + +void GDKWindow::Minimize() +{ + ASSERT(HasHWND()); + ShowWindow(_handle, SW_MINIMIZE); +} + +void GDKWindow::Maximize() +{ + ASSERT(HasHWND()); + ShowWindow(_handle, SW_MAXIMIZE); +} + +void GDKWindow::Restore() +{ + ASSERT(HasHWND()); + ShowWindow(_handle, SW_RESTORE); +} + +bool GDKWindow::IsClosed() const +{ + return !HasHWND(); +} + +bool GDKWindow::IsForegroundWindow() const +{ + return Platform::GetHasFocus(); +} + +void GDKWindow::SetIsFullscreen(bool isFullscreen) +{ +} + +void GDKWindow::GetScreenInfo(int32& x, int32& y, int32& width, int32& height) const +{ + x = 0; + y = 0; + width = (int32)_clientSize.X; + height = (int32)_clientSize.Y; +} + +void GDKWindow::SetCursor(CursorType type) +{ + // Base + WindowBase::SetCursor(type); + + UpdateCursor(); +} + +void GDKWindow::CheckForWindowResize() +{ + // Skip for minimized window (GetClientRect for minimized window returns 0) + if (_minimized) + return; + + ASSERT(HasHWND()); + + // Cache client size + RECT rect; + GetClientRect(_handle, &rect); + const int32 width = Math::Max(rect.right - rect.left, 0L); + const int32 height = Math::Max(rect.bottom - rect.top, 0L); + _clientSize = Vector2(static_cast(width), static_cast(height)); + + // Check if window size has been changed + if (width > 0 && height > 0 && (_swapChain == nullptr || width != _swapChain->GetWidth() || height != _swapChain->GetHeight())) + { + OnResize(width, height); + } +} + +void GDKWindow::UpdateCursor() const +{ + if (_cursor == CursorType::Hidden) + { + ::SetCursor(nullptr); + return; + } + + int32 index = 0; + switch (_cursor) + { + case CursorType::Default: + break; + case CursorType::Cross: + index = 1; + break; + case CursorType::Hand: + index = 2; + break; + case CursorType::Help: + index = 3; + break; + case CursorType::IBeam: + index = 4; + break; + case CursorType::No: + index = 5; + break; + case CursorType::Wait: + index = 11; + break; + case CursorType::SizeAll: + index = 6; + break; + case CursorType::SizeNESW: + index = 7; + break; + case CursorType::SizeNS: + index = 8; + break; + case CursorType::SizeNWSE: + index = 9; + break; + case CursorType::SizeWE: + index = 10; + break; + } + + static const LPCWSTR cursors[] = + { + IDC_ARROW, + IDC_CROSS, + IDC_HAND, + IDC_HELP, + IDC_IBEAM, + IDC_NO, + IDC_SIZEALL, + IDC_SIZENESW, + IDC_SIZENS, + IDC_SIZENWSE, + IDC_SIZEWE, + IDC_WAIT, + }; + + ASSERT(index >= 0 && index < ARRAY_COUNT(cursors)); + const HCURSOR cursor = LoadCursorW(nullptr, cursors[index]); + ::SetCursor(cursor); +} + +Windows::LRESULT GDKWindow::WndProc(Windows::UINT msg, Windows::WPARAM wParam, Windows::LPARAM lParam) +{ + switch (msg) + { + case WM_SETCURSOR: + { + if (LOWORD(lParam) == HTCLIENT) + { + UpdateCursor(); + return true; + } + break; + } + case WM_CREATE: + { + return 0; + } + case WM_SIZE: + { + if (SIZE_MINIMIZED == wParam) + { + // Set flags + _minimized = true; + _maximized = false; + } + else + { + RECT rcCurrentClient; + GetClientRect(_handle, &rcCurrentClient); + if (rcCurrentClient.top == 0 && rcCurrentClient.bottom == 0) + { + // Rapidly clicking the task bar to minimize and restore a window can cause a WM_SIZE message with SIZE_RESTORED when + // the window has actually become minimized due to rapid change so just ignore this message. + } + else if (SIZE_MAXIMIZED == wParam) + { + // Set flags + _minimized = false; + _maximized = true; + + // Check size + CheckForWindowResize(); + } + else if (SIZE_RESTORED == wParam) + { + if (_maximized) + { + // Clear flag + _maximized = false; + + // Check size + CheckForWindowResize(); + } + else if (_minimized) + { + // Clear flag + _minimized = false; + + // Check size + CheckForWindowResize(); + } + else + { + // This WM_SIZE come from resizing the window via an API like SetWindowPos() so resize + CheckForWindowResize(); + } + } + } + break; + } + case WM_SETFOCUS: + OnGotFocus(); + break; + case WM_KILLFOCUS: + OnLostFocus(); + break; + case WM_ACTIVATEAPP: + if (wParam == TRUE && !_focused) + { + OnGotFocus(); + } + else if (wParam == FALSE && _focused) + { + OnLostFocus(); + } + break; + case WM_CLOSE: + Close(ClosingReason::User); + return 0; + case WM_DESTROY: + { + // Quit + PostQuitMessage(0); + return 0; + } + } + + return DefWindowProc(_handle, msg, wParam, lParam); +} + +#endif diff --git a/Source/Engine/Platform/GDK/GDKWindow.h b/Source/Engine/Platform/GDK/GDKWindow.h new file mode 100644 index 000000000..f662ab24a --- /dev/null +++ b/Source/Engine/Platform/GDK/GDKWindow.h @@ -0,0 +1,93 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_GDK + +#include "Engine/Platform/Base/WindowBase.h" +#include "Engine/Platform/Platform.h" +#include "Engine/Platform/Win32/WindowsMinimal.h" + +/// +/// Implementation of the window class for GDK platform. +/// +class GDKWindow : public WindowBase +{ + friend GDKPlatform; +private: + + Windows::HWND _handle; + Vector2 _clientSize; + +public: + + /// + /// Initializes a new instance of the class. + /// + /// The initial window settings. + GDKWindow(const CreateWindowSettings& settings); + + /// + /// Finalizes an instance of the class. + /// + ~GDKWindow(); + +public: + + /// + /// Gets the window handle. + /// + FORCE_INLINE Windows::HWND GetHWND() const + { + return _handle; + } + + /// + /// Checks if the window has valid handle created. + /// + FORCE_INLINE bool HasHWND() const + { + return _handle != nullptr; + } + + /// + /// Gets the information about screen which contains window. + /// + /// The x position. + /// The y position. + /// The width. + /// The height. + void GetScreenInfo(int32& x, int32& y, int32& width, int32& height) const; + +public: + + /// + /// The Windows messages procedure. + /// + /// The mMessage. + /// The first parameter. + /// The second parameter. + /// The result. + Windows::LRESULT WndProc(Windows::UINT msg, Windows::WPARAM wParam, Windows::LPARAM lParam); + +private: + + void CheckForWindowResize(); + void UpdateCursor() const; + +public: + + // [Window] + void* GetNativePtr() const override; + void Show() override; + void Hide() override; + void Minimize() override; + void Maximize() override; + void Restore() override; + bool IsClosed() const override; + bool IsForegroundWindow() const override; + void SetIsFullscreen(bool isFullscreen) override; + void SetCursor(CursorType type) override; +}; + +#endif diff --git a/Source/Engine/Platform/Platform.Build.cs b/Source/Engine/Platform/Platform.Build.cs index 5c32d74c4..68c284acd 100644 --- a/Source/Engine/Platform/Platform.Build.cs +++ b/Source/Engine/Platform/Platform.Build.cs @@ -58,10 +58,12 @@ public class Platform : EngineModule break; case TargetPlatform.XboxOne: options.SourcePaths.Add(Path.Combine(FolderPath, "Win32")); + options.SourcePaths.Add(Path.Combine(FolderPath, "GDK")); options.SourcePaths.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "XboxOne", "Engine", "Platform")); break; case TargetPlatform.XboxScarlett: options.SourcePaths.Add(Path.Combine(FolderPath, "Win32")); + options.SourcePaths.Add(Path.Combine(FolderPath, "GDK")); options.SourcePaths.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "XboxScarlett", "Engine", "Platform")); break; case TargetPlatform.Android: diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h index 1fa6189f4..ae402bb1a 100644 --- a/Source/Engine/Platform/Types.h +++ b/Source/Engine/Platform/Types.h @@ -111,8 +111,8 @@ class XboxOnePlatform; typedef XboxOnePlatform Platform; class Win32Thread; typedef Win32Thread Thread; -class XboxOneWindow; -typedef XboxOneWindow Window; +class GDKWindow; +typedef GDKWindow Window; class NetworkBase; typedef NetworkBase Network; @@ -134,8 +134,8 @@ class XboxScarlettPlatform; typedef XboxScarlettPlatform Platform; class Win32Thread; typedef Win32Thread Thread; -class XboxScarlettWindow; -typedef XboxScarlettWindow Window; +class GDKWindow; +typedef GDKWindow Window; class NetworkBase; typedef NetworkBase Network; diff --git a/Source/Engine/Platform/Window.h b/Source/Engine/Platform/Window.h index eec449dae..0a68ea9c7 100644 --- a/Source/Engine/Platform/Window.h +++ b/Source/Engine/Platform/Window.h @@ -11,9 +11,9 @@ #elif PLATFORM_PS4 #include "Platforms/PS4/Engine/Platform/PS4Window.h" #elif PLATFORM_XBOX_ONE -#include "Platforms/XboxOne/Engine/Platform/XboxOneWindow.h" +#include "GDK/GDKWindow.h" #elif PLATFORM_XBOX_SCARLETT -#include "Platforms/XboxScarlett/Engine/Platform/XboxScarlettWindow.h" +#include "GDK/GDKWindow.h" #elif PLATFORM_ANDROID #include "Android/AndroidWindow.h" #elif PLATFORM_SWITCH diff --git a/Source/Tools/Flax.Build/Platforms/GDK/GDK.cs b/Source/Tools/Flax.Build/Platforms/GDK/GDK.cs new file mode 100644 index 000000000..ff01a3d5f --- /dev/null +++ b/Source/Tools/Flax.Build/Platforms/GDK/GDK.cs @@ -0,0 +1,69 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.IO; +using System.Linq; + +namespace Flax.Build.Platforms +{ + /// + /// The Microsoft Game Development Kit. + /// + /// + public sealed class GDK : Sdk + { + /// + /// The singleton instance. + /// + public static readonly GDK Instance = new GDK(); + + /// + public override TargetPlatform[] Platforms => new[] + { + TargetPlatform.Windows, + }; + + /// + /// Initializes a new instance of the class. + /// + public GDK() + { + if (!Platforms.Contains(Platform.BuildTargetPlatform)) + return; + + var sdkDir = Environment.GetEnvironmentVariable("GameDKLatest"); + if (!Directory.Exists(sdkDir)) + sdkDir = Environment.GetEnvironmentVariable("GRDKLatest"); + if (Directory.Exists(sdkDir)) + { + if (sdkDir.EndsWith("GRDK\\")) + sdkDir = sdkDir.Remove(sdkDir.Length - 6); + else if (sdkDir.EndsWith("GRDK")) + sdkDir = sdkDir.Remove(sdkDir.Length - 5); + RootPath = sdkDir; + + // Read the SDK version number + string sdkManifest = Path.Combine(RootPath, "GRDK", "grdk.ini"); + if (File.Exists(sdkManifest)) + { + var contents = File.ReadAllText(sdkManifest); + const string prefix = "_xbld_edition="; + var start = contents.IndexOf(prefix) + prefix.Length; + var end = contents.IndexOf("\r\n_xbld_full_productbuild"); + var versionText = contents.Substring(start, end - start); + Version = new Version(int.Parse(versionText), 0); + + var minEdition = 200500; + if (Version.Major < minEdition) + { + Log.Error(string.Format("Unsupported GDK version {0}. Minimum supported is edition {1}.", Version.Major, minEdition)); + return; + } + + Log.Info(string.Format("Found GDK {0} at {1}", Version.Major, RootPath)); + IsValid = true; + } + } + } + } +} diff --git a/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs b/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs new file mode 100644 index 000000000..82005e9bf --- /dev/null +++ b/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.Linq; +using Flax.Build.Projects.VisualStudio; + +namespace Flax.Build.Platforms +{ + /// + /// The GDK platform implementation. + /// + /// + /// + public abstract class GDKPlatform : WindowsPlatformBase + { + /// + /// Initializes a new instance of the class. + /// + protected GDKPlatform() + { + // Visual Studio 2017 or newer required + var visualStudio = VisualStudioInstance.GetInstances().FirstOrDefault(x => x.Version == VisualStudioVersion.VisualStudio2017 || x.Version == VisualStudioVersion.VisualStudio2019); + if (visualStudio == null) + _hasRequiredSDKsInstalled = false; + + // Windows 10.0.19041.0 SDK or newer required + var sdks = GetSDKs(); + if (!sdks.ContainsKey(WindowsPlatformSDK.v10_0_19041_0)) + _hasRequiredSDKsInstalled = false; + + // v141 toolset or newer required + var toolsets = GetToolsets(); + if (!toolsets.ContainsKey(WindowsPlatformToolset.v141) && + !toolsets.ContainsKey(WindowsPlatformToolset.v142)) + _hasRequiredSDKsInstalled = false; + } + } +} diff --git a/Source/Tools/Flax.Build/Platforms/GDK/GDKToolchain.cs b/Source/Tools/Flax.Build/Platforms/GDK/GDKToolchain.cs new file mode 100644 index 000000000..89f9f88b5 --- /dev/null +++ b/Source/Tools/Flax.Build/Platforms/GDK/GDKToolchain.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.IO; +using Flax.Build.NativeCpp; + +namespace Flax.Build.Platforms +{ + /// + /// The GDK toolchain implementation. + /// + /// + /// + public abstract class GDKToolchain : WindowsToolchainBase + { + /// + /// Initializes a new instance of the class. + /// + /// The platform. + /// The architecture. + protected GDKToolchain(GDKPlatform platform, TargetArchitecture architecture) + : base(platform, architecture, WindowsPlatformToolset.Latest, WindowsPlatformSDK.v10_0_19041_0) + { + // Setup system paths + SystemIncludePaths.Add(Path.Combine(GDK.Instance.RootPath, "GRDK\\GameKit\\Include")); + SystemLibraryPaths.Add(Path.Combine(GDK.Instance.RootPath, "GRDK\\GameKit\\Lib\\amd64")); + SystemLibraryPaths.Add(Path.Combine(GDK.Instance.RootPath, "GRDK\\ExtensionLibraries\\Xbox.Services.API.C\\DesignTime\\CommonConfiguration\\Neutral\\Lib\\Release\\" + Toolset)); + } + + /// + public override void SetupEnvironment(BuildOptions options) + { + base.SetupEnvironment(options); + + options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_GDK"); + options.CompileEnv.PreprocessorDefinitions.Add("WINAPI_FAMILY=WINAPI_FAMILY_GAMES"); + options.CompileEnv.PreprocessorDefinitions.Add("_ATL_NO_DEFAULT_LIBS"); + options.CompileEnv.PreprocessorDefinitions.Add("__WRL_NO_DEFAULT_LIB__"); + + options.LinkEnv.InputLibraries.Add("xgameruntime.lib"); + options.LinkEnv.InputLibraries.Add("xgameplatform.lib"); + options.LinkEnv.InputLibraries.Add("Microsoft.Xbox.Services.142.GDK.C.lib"); + + var toolsetPath = WindowsPlatformBase.GetToolsets()[Toolset]; + var toolsPath = WindowsPlatformBase.GetVCToolPath64(Toolset); + if (options.CompileEnv.UseDebugCRT) + throw new Exception("Don't use debug CRT on GDK."); + var name = Path.GetFileName(toolsetPath); + var redistToolsPath = Path.Combine(toolsPath, "..", "..", "..", "..", "..", "..", "Redist/MSVC/"); + var paths = Directory.GetDirectories(redistToolsPath, name.Substring(0, 5) + "*"); + redistToolsPath = Path.Combine(paths[0], "x64", "Microsoft.VC" + (int)Toolset + ".CRT"); + redistToolsPath = Utilities.RemovePathRelativeParts(redistToolsPath); + options.DependencyFiles.Add(Path.Combine(redistToolsPath, "concrt140.dll")); + options.DependencyFiles.Add(Path.Combine(redistToolsPath, "msvcp140.dll")); + options.DependencyFiles.Add(Path.Combine(redistToolsPath, "msvcp140_1.dll")); + options.DependencyFiles.Add(Path.Combine(redistToolsPath, "msvcp140_2.dll")); + options.DependencyFiles.Add(Path.Combine(redistToolsPath, "msvcp140_codecvt_ids.dll")); + options.DependencyFiles.Add(Path.Combine(redistToolsPath, "vccorlib140.dll")); + options.DependencyFiles.Add(Path.Combine(redistToolsPath, "vcruntime140.dll")); + options.DependencyFiles.Add(Path.Combine(redistToolsPath, "vcruntime140_1.dll")); + } + } +}