diff options
author | rtk0c <[email protected]> | 2021-04-10 21:13:34 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2021-04-10 21:13:34 -0700 |
commit | 568fcc1dfe40c37b57b7baa2dea93b291d3fa956 (patch) | |
tree | 826b410c502a950c1d4804d351959da914003b36 | |
parent | 4303d0be47526b35e5bb3e3be001da227dae5d96 (diff) |
Add dx11, dx12, and vulkan backends
25 files changed, 2034 insertions, 419 deletions
diff --git a/3rdparty/imgui/backend/imgui_impl_win32.cpp b/3rdparty/imgui/backend/imgui_impl_win32.cpp new file mode 100644 index 0000000..39e381f --- /dev/null +++ b/3rdparty/imgui/backend/imgui_impl_win32.cpp @@ -0,0 +1,542 @@ +// dear imgui: Platform Backend for Windows (standard windows API for 32 and 64 bits applications) +// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..) + +// Implemented features: +// [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui) +// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. +// [X] Platform: Keyboard arrays indexed using VK_* Virtual Key Codes, e.g. ImGui::IsKeyPressed(VK_SPACE). +// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. + +// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. +// Read online: https://github.com/ocornut/imgui/tree/master/docs + +#include "imgui.h" +#include "imgui_impl_win32.h" +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <tchar.h> +#include <dwmapi.h> + +// Configuration flags to add in your imconfig.h file: +//#define IMGUI_IMPL_WIN32_DISABLE_GAMEPAD // Disable gamepad support (this used to be meaningful before <1.81) but we know load XInput dynamically so the option is less relevant now. + +// Using XInput for gamepad (will load DLL dynamically) +#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD +#include <XInput.h> +typedef DWORD (WINAPI *PFN_XInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES*); +typedef DWORD (WINAPI *PFN_XInputGetState)(DWORD, XINPUT_STATE*); +#endif + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2021-03-23: Inputs: Clearing keyboard down array when losing focus (WM_KILLFOCUS). +// 2021-02-18: Added ImGui_ImplWin32_EnableAlphaCompositing(). Non Visual Studio users will need to link with dwmapi.lib (MinGW/gcc: use -ldwmapi). +// 2021-02-17: Fixed ImGui_ImplWin32_EnableDpiAwareness() attempting to get SetProcessDpiAwareness from shcore.dll on Windows 8 whereas it is only supported on Windows 8.1. +// 2021-01-25: Inputs: Dynamically loading XInput DLL. +// 2020-12-04: Misc: Fixed setting of io.DisplaySize to invalid/uninitialized data when after hwnd has been closed. +// 2020-03-03: Inputs: Calling AddInputCharacterUTF16() to support surrogate pairs leading to codepoint >= 0x10000 (for more complete CJK inputs) +// 2020-02-17: Added ImGui_ImplWin32_EnableDpiAwareness(), ImGui_ImplWin32_GetDpiScaleForHwnd(), ImGui_ImplWin32_GetDpiScaleForMonitor() helper functions. +// 2020-01-14: Inputs: Added support for #define IMGUI_IMPL_WIN32_DISABLE_GAMEPAD/IMGUI_IMPL_WIN32_DISABLE_LINKING_XINPUT. +// 2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor. +// 2019-05-11: Inputs: Don't filter value from WM_CHAR before calling AddInputCharacter(). +// 2019-01-17: Misc: Using GetForegroundWindow()+IsChild() instead of GetActiveWindow() to be compatible with windows created in a different thread or parent. +// 2019-01-17: Inputs: Added support for mouse buttons 4 and 5 via WM_XBUTTON* messages. +// 2019-01-15: Inputs: Added support for XInput gamepads (if ImGuiConfigFlags_NavEnableGamepad is set by user application). +// 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window. +// 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor. +// 2018-06-10: Inputs: Fixed handling of mouse wheel messages to support fine position messages (typically sent by track-pads). +// 2018-06-08: Misc: Extracted imgui_impl_win32.cpp/.h away from the old combined DX9/DX10/DX11/DX12 examples. +// 2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors and ImGuiBackendFlags_HasSetMousePos flags + honor ImGuiConfigFlags_NoMouseCursorChange flag. +// 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value and WM_SETCURSOR message handling). +// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. +// 2018-02-06: Inputs: Honoring the io.WantSetMousePos by repositioning the mouse (when using navigation and ImGuiConfigFlags_NavMoveMouse is set). +// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. +// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support. +// 2018-01-08: Inputs: Added mapping for ImGuiKey_Insert. +// 2018-01-05: Inputs: Added WM_LBUTTONDBLCLK double-click handlers for window classes with the CS_DBLCLKS flag. +// 2017-10-23: Inputs: Added WM_SYSKEYDOWN / WM_SYSKEYUP handlers so e.g. the VK_MENU key can be read. +// 2017-10-23: Inputs: Using Win32 ::SetCapture/::GetCapture() to retrieve mouse positions outside the client area when dragging. +// 2016-11-12: Inputs: Only call Win32 ::SetCursor(NULL) when io.MouseDrawCursor is set. + +// Win32 Data +static HWND g_hWnd = NULL; +static INT64 g_Time = 0; +static INT64 g_TicksPerSecond = 0; +static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT; +static bool g_HasGamepad = false; +static bool g_WantUpdateHasGamepad = true; + +// XInput DLL and functions +#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD +static HMODULE g_XInputDLL = NULL; +static PFN_XInputGetCapabilities g_XInputGetCapabilities = NULL; +static PFN_XInputGetState g_XInputGetState = NULL; +#endif + +// Functions +bool ImGui_ImplWin32_Init(void* hwnd) +{ + if (!::QueryPerformanceFrequency((LARGE_INTEGER*)&g_TicksPerSecond)) + return false; + if (!::QueryPerformanceCounter((LARGE_INTEGER*)&g_Time)) + return false; + + // Setup backend capabilities flags + g_hWnd = (HWND)hwnd; + ImGuiIO& io = ImGui::GetIO(); + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) + io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) + io.BackendPlatformName = "imgui_impl_win32"; + io.ImeWindowHandle = hwnd; + + // Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeysDown[] array that we will update during the application lifetime. + io.KeyMap[ImGuiKey_Tab] = VK_TAB; + io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = VK_UP; + io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN; + io.KeyMap[ImGuiKey_PageUp] = VK_PRIOR; + io.KeyMap[ImGuiKey_PageDown] = VK_NEXT; + io.KeyMap[ImGuiKey_Home] = VK_HOME; + io.KeyMap[ImGuiKey_End] = VK_END; + io.KeyMap[ImGuiKey_Insert] = VK_INSERT; + io.KeyMap[ImGuiKey_Delete] = VK_DELETE; + io.KeyMap[ImGuiKey_Backspace] = VK_BACK; + io.KeyMap[ImGuiKey_Space] = VK_SPACE; + io.KeyMap[ImGuiKey_Enter] = VK_RETURN; + io.KeyMap[ImGuiKey_Escape] = VK_ESCAPE; + io.KeyMap[ImGuiKey_KeyPadEnter] = VK_RETURN; + io.KeyMap[ImGuiKey_A] = 'A'; + io.KeyMap[ImGuiKey_C] = 'C'; + io.KeyMap[ImGuiKey_V] = 'V'; + io.KeyMap[ImGuiKey_X] = 'X'; + io.KeyMap[ImGuiKey_Y] = 'Y'; + io.KeyMap[ImGuiKey_Z] = 'Z'; + + // Dynamically load XInput library +#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD + const char* xinput_dll_names[] = + { + "xinput1_4.dll", // Windows 8+ + "xinput1_3.dll", // DirectX SDK + "xinput9_1_0.dll", // Windows Vista, Windows 7 + "xinput1_2.dll", // DirectX SDK + "xinput1_1.dll" // DirectX SDK + }; + for (int n = 0; n < IM_ARRAYSIZE(xinput_dll_names); n++) + if (HMODULE dll = ::LoadLibraryA(xinput_dll_names[n])) + { + g_XInputDLL = dll; + g_XInputGetCapabilities = (PFN_XInputGetCapabilities)::GetProcAddress(dll, "XInputGetCapabilities"); + g_XInputGetState = (PFN_XInputGetState)::GetProcAddress(dll, "XInputGetState"); + break; + } +#endif // IMGUI_IMPL_WIN32_DISABLE_GAMEPAD + + return true; +} + +void ImGui_ImplWin32_Shutdown() +{ + // Unload XInput library +#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD + if (g_XInputDLL) + ::FreeLibrary(g_XInputDLL); + g_XInputDLL = NULL; + g_XInputGetCapabilities = NULL; + g_XInputGetState = NULL; +#endif // IMGUI_IMPL_WIN32_DISABLE_GAMEPAD + + g_hWnd = NULL; + g_Time = 0; + g_TicksPerSecond = 0; + g_LastMouseCursor = ImGuiMouseCursor_COUNT; + g_HasGamepad = false; + g_WantUpdateHasGamepad = true; +} + +static bool ImGui_ImplWin32_UpdateMouseCursor() +{ + ImGuiIO& io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) + return false; + + ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); + if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor) + { + // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor + ::SetCursor(NULL); + } + else + { + // Show OS mouse cursor + LPTSTR win32_cursor = IDC_ARROW; + switch (imgui_cursor) + { + case ImGuiMouseCursor_Arrow: win32_cursor = IDC_ARROW; break; + case ImGuiMouseCursor_TextInput: win32_cursor = IDC_IBEAM; break; + case ImGuiMouseCursor_ResizeAll: win32_cursor = IDC_SIZEALL; break; + case ImGuiMouseCursor_ResizeEW: win32_cursor = IDC_SIZEWE; break; + case ImGuiMouseCursor_ResizeNS: win32_cursor = IDC_SIZENS; break; + case ImGuiMouseCursor_ResizeNESW: win32_cursor = IDC_SIZENESW; break; + case ImGuiMouseCursor_ResizeNWSE: win32_cursor = IDC_SIZENWSE; break; + case ImGuiMouseCursor_Hand: win32_cursor = IDC_HAND; break; + case ImGuiMouseCursor_NotAllowed: win32_cursor = IDC_NO; break; + } + ::SetCursor(::LoadCursor(NULL, win32_cursor)); + } + return true; +} + +static void ImGui_ImplWin32_UpdateMousePos() +{ + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(g_hWnd != 0); + + // Set OS mouse position if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) + if (io.WantSetMousePos) + { + POINT pos = { (int)io.MousePos.x, (int)io.MousePos.y }; + if (::ClientToScreen(g_hWnd, &pos)) + ::SetCursorPos(pos.x, pos.y); + } + + // Set mouse position + io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + POINT pos; + if (HWND active_window = ::GetForegroundWindow()) + if (active_window == g_hWnd || ::IsChild(active_window, g_hWnd)) + if (::GetCursorPos(&pos) && ::ScreenToClient(g_hWnd, &pos)) + io.MousePos = ImVec2((float)pos.x, (float)pos.y); +} + +// Gamepad navigation mapping +static void ImGui_ImplWin32_UpdateGamepads() +{ +#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD + ImGuiIO& io = ImGui::GetIO(); + memset(io.NavInputs, 0, sizeof(io.NavInputs)); + if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) + return; + + // Calling XInputGetState() every frame on disconnected gamepads is unfortunately too slow. + // Instead we refresh gamepad availability by calling XInputGetCapabilities() _only_ after receiving WM_DEVICECHANGE. + if (g_WantUpdateHasGamepad) + { + XINPUT_CAPABILITIES caps; + g_HasGamepad = g_XInputGetCapabilities ? (g_XInputGetCapabilities(0, XINPUT_FLAG_GAMEPAD, &caps) == ERROR_SUCCESS) : false; + g_WantUpdateHasGamepad = false; + } + + io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; + XINPUT_STATE xinput_state; + if (g_HasGamepad && g_XInputGetState && g_XInputGetState(0, &xinput_state) == ERROR_SUCCESS) + { + const XINPUT_GAMEPAD& gamepad = xinput_state.Gamepad; + io.BackendFlags |= ImGuiBackendFlags_HasGamepad; + +#define MAP_BUTTON(NAV_NO, BUTTON_ENUM) { io.NavInputs[NAV_NO] = (gamepad.wButtons & BUTTON_ENUM) ? 1.0f : 0.0f; } +#define MAP_ANALOG(NAV_NO, VALUE, V0, V1) { float vn = (float)(VALUE - V0) / (float)(V1 - V0); if (vn > 1.0f) vn = 1.0f; if (vn > 0.0f && io.NavInputs[NAV_NO] < vn) io.NavInputs[NAV_NO] = vn; } + MAP_BUTTON(ImGuiNavInput_Activate, XINPUT_GAMEPAD_A); // Cross / A + MAP_BUTTON(ImGuiNavInput_Cancel, XINPUT_GAMEPAD_B); // Circle / B + MAP_BUTTON(ImGuiNavInput_Menu, XINPUT_GAMEPAD_X); // Square / X + MAP_BUTTON(ImGuiNavInput_Input, XINPUT_GAMEPAD_Y); // Triangle / Y + MAP_BUTTON(ImGuiNavInput_DpadLeft, XINPUT_GAMEPAD_DPAD_LEFT); // D-Pad Left + MAP_BUTTON(ImGuiNavInput_DpadRight, XINPUT_GAMEPAD_DPAD_RIGHT); // D-Pad Right + MAP_BUTTON(ImGuiNavInput_DpadUp, XINPUT_GAMEPAD_DPAD_UP); // D-Pad Up + MAP_BUTTON(ImGuiNavInput_DpadDown, XINPUT_GAMEPAD_DPAD_DOWN); // D-Pad Down + MAP_BUTTON(ImGuiNavInput_FocusPrev, XINPUT_GAMEPAD_LEFT_SHOULDER); // L1 / LB + MAP_BUTTON(ImGuiNavInput_FocusNext, XINPUT_GAMEPAD_RIGHT_SHOULDER); // R1 / RB + MAP_BUTTON(ImGuiNavInput_TweakSlow, XINPUT_GAMEPAD_LEFT_SHOULDER); // L1 / LB + MAP_BUTTON(ImGuiNavInput_TweakFast, XINPUT_GAMEPAD_RIGHT_SHOULDER); // R1 / RB + MAP_ANALOG(ImGuiNavInput_LStickLeft, gamepad.sThumbLX, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768); + MAP_ANALOG(ImGuiNavInput_LStickRight, gamepad.sThumbLX, +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767); + MAP_ANALOG(ImGuiNavInput_LStickUp, gamepad.sThumbLY, +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767); + MAP_ANALOG(ImGuiNavInput_LStickDown, gamepad.sThumbLY, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32767); +#undef MAP_BUTTON +#undef MAP_ANALOG + } +#endif // #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD +} + +void ImGui_ImplWin32_NewFrame() +{ + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame()."); + + // Setup display size (every frame to accommodate for window resizing) + RECT rect = { 0, 0, 0, 0 }; + ::GetClientRect(g_hWnd, &rect); + io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); + + // Setup time step + INT64 current_time = 0; + ::QueryPerformanceCounter((LARGE_INTEGER*)¤t_time); + io.DeltaTime = (float)(current_time - g_Time) / g_TicksPerSecond; + g_Time = current_time; + + // Read keyboard modifiers inputs + io.KeyCtrl = (::GetKeyState(VK_CONTROL) & 0x8000) != 0; + io.KeyShift = (::GetKeyState(VK_SHIFT) & 0x8000) != 0; + io.KeyAlt = (::GetKeyState(VK_MENU) & 0x8000) != 0; + io.KeySuper = false; + // io.KeysDown[], io.MousePos, io.MouseDown[], io.MouseWheel: filled by the WndProc handler below. + + // Update OS mouse position + ImGui_ImplWin32_UpdateMousePos(); + + // Update OS mouse cursor with the cursor requested by imgui + ImGuiMouseCursor mouse_cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor(); + if (g_LastMouseCursor != mouse_cursor) + { + g_LastMouseCursor = mouse_cursor; + ImGui_ImplWin32_UpdateMouseCursor(); + } + + // Update game controllers (if enabled and available) + ImGui_ImplWin32_UpdateGamepads(); +} + +// Allow compilation with old Windows SDK. MinGW doesn't have default _WIN32_WINNT/WINVER versions. +#ifndef WM_MOUSEHWHEEL +#define WM_MOUSEHWHEEL 0x020E +#endif +#ifndef DBT_DEVNODES_CHANGED +#define DBT_DEVNODES_CHANGED 0x0007 +#endif + +// Win32 message handler (process Win32 mouse/keyboard inputs, etc.) +// Call from your application's message handler. +// When implementing your own backend, you can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if Dear ImGui wants to use your inputs. +// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. +// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. +// Generally you may always pass all inputs to Dear ImGui, and hide them from your application based on those two flags. +// PS: In this Win32 handler, we use the capture API (GetCapture/SetCapture/ReleaseCapture) to be able to read mouse coordinates when dragging mouse outside of our window bounds. +// PS: We treat DBLCLK messages as regular mouse down messages, so this code will work on windows classes that have the CS_DBLCLKS flag set. Our own example app code doesn't set this flag. +#if 0 +// Copy this line into your .cpp file to forward declare the function. +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); +#endif +IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (ImGui::GetCurrentContext() == NULL) + return 0; + + ImGuiIO& io = ImGui::GetIO(); + switch (msg) + { + case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: + case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK: + case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK: + { + int button = 0; + if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONDBLCLK) { button = 0; } + if (msg == WM_RBUTTONDOWN || msg == WM_RBUTTONDBLCLK) { button = 1; } + if (msg == WM_MBUTTONDOWN || msg == WM_MBUTTONDBLCLK) { button = 2; } + if (msg == WM_XBUTTONDOWN || msg == WM_XBUTTONDBLCLK) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; } + if (!ImGui::IsAnyMouseDown() && ::GetCapture() == NULL) + ::SetCapture(hwnd); + io.MouseDown[button] = true; + return 0; + } + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + case WM_XBUTTONUP: + { + int button = 0; + if (msg == WM_LBUTTONUP) { button = 0; } + if (msg == WM_RBUTTONUP) { button = 1; } + if (msg == WM_MBUTTONUP) { button = 2; } + if (msg == WM_XBUTTONUP) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; } + io.MouseDown[button] = false; + if (!ImGui::IsAnyMouseDown() && ::GetCapture() == hwnd) + ::ReleaseCapture(); + return 0; + } + case WM_MOUSEWHEEL: + io.MouseWheel += (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA; + return 0; + case WM_MOUSEHWHEEL: + io.MouseWheelH += (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA; + return 0; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + if (wParam < 256) + io.KeysDown[wParam] = 1; + return 0; + case WM_KEYUP: + case WM_SYSKEYUP: + if (wParam < 256) + io.KeysDown[wParam] = 0; + return 0; + case WM_KILLFOCUS: + memset(io.KeysDown, 0, sizeof(io.KeysDown)); + return 0; + case WM_CHAR: + // You can also use ToAscii()+GetKeyboardState() to retrieve characters. + if (wParam > 0 && wParam < 0x10000) + io.AddInputCharacterUTF16((unsigned short)wParam); + return 0; + case WM_SETCURSOR: + if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor()) + return 1; + return 0; + case WM_DEVICECHANGE: + if ((UINT)wParam == DBT_DEVNODES_CHANGED) + g_WantUpdateHasGamepad = true; + return 0; + } + return 0; +} + + +//-------------------------------------------------------------------------------------------------------- +// DPI-related helpers (optional) +//-------------------------------------------------------------------------------------------------------- +// - Use to enable DPI awareness without having to create an application manifest. +// - Your own app may already do this via a manifest or explicit calls. This is mostly useful for our examples/ apps. +// - In theory we could call simple functions from Windows SDK such as SetProcessDPIAware(), SetProcessDpiAwareness(), etc. +// but most of the functions provided by Microsoft require Windows 8.1/10+ SDK at compile time and Windows 8/10+ at runtime, +// neither we want to require the user to have. So we dynamically select and load those functions to avoid dependencies. +//--------------------------------------------------------------------------------------------------------- +// This is the scheme successfully used by GLFW (from which we borrowed some of the code) and other apps aiming to be highly portable. +// ImGui_ImplWin32_EnableDpiAwareness() is just a helper called by main.cpp, we don't call it automatically. +// If you are trying to implement your own backend for your own engine, you may ignore that noise. +//--------------------------------------------------------------------------------------------------------- + +// Implement some of the functions and types normally declared in recent Windows SDK. +#if !defined(_versionhelpers_H_INCLUDED_) && !defined(_INC_VERSIONHELPERS) +static BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp) +{ + OSVERSIONINFOEXW osvi = { sizeof(osvi), major, minor, 0, 0, { 0 }, sp, 0, 0, 0, 0 }; + DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR; + ULONGLONG cond = ::VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL); + cond = ::VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL); + cond = ::VerSetConditionMask(cond, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + return ::VerifyVersionInfoW(&osvi, mask, cond); +} +#define IsWindowsVistaOrGreater() IsWindowsVersionOrGreater(HIBYTE(0x0600), LOBYTE(0x0600), 0) // _WIN32_WINNT_VISTA +#define IsWindows8OrGreater() IsWindowsVersionOrGreater(HIBYTE(0x0602), LOBYTE(0x0602), 0) // _WIN32_WINNT_WIN8 +#define IsWindows8Point1OrGreater() IsWindowsVersionOrGreater(HIBYTE(0x0603), LOBYTE(0x0603), 0) // _WIN32_WINNT_WINBLUE +#endif + +#ifndef DPI_ENUMS_DECLARED +typedef enum { PROCESS_DPI_UNAWARE = 0, PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS; +typedef enum { MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2, MDT_DEFAULT = MDT_EFFECTIVE_DPI } MONITOR_DPI_TYPE; +#endif +#ifndef _DPI_AWARENESS_CONTEXTS_ +DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); +#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE (DPI_AWARENESS_CONTEXT)-3 +#endif +#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 +#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 (DPI_AWARENESS_CONTEXT)-4 +#endif +typedef HRESULT(WINAPI* PFN_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS); // Shcore.lib + dll, Windows 8.1+ +typedef HRESULT(WINAPI* PFN_GetDpiForMonitor)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); // Shcore.lib + dll, Windows 8.1+ +typedef DPI_AWARENESS_CONTEXT(WINAPI* PFN_SetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT); // User32.lib + dll, Windows 10 v1607+ (Creators Update) + +// Helper function to enable DPI awareness without setting up a manifest +void ImGui_ImplWin32_EnableDpiAwareness() +{ + // if (IsWindows10OrGreater()) // This needs a manifest to succeed. Instead we try to grab the function pointer! + { + static HINSTANCE user32_dll = ::LoadLibraryA("user32.dll"); // Reference counted per-process + if (PFN_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContextFn = (PFN_SetThreadDpiAwarenessContext)::GetProcAddress(user32_dll, "SetThreadDpiAwarenessContext")) + { + SetThreadDpiAwarenessContextFn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + return; + } + } + if (IsWindows8Point1OrGreater()) + { + static HINSTANCE shcore_dll = ::LoadLibraryA("shcore.dll"); // Reference counted per-process + if (PFN_SetProcessDpiAwareness SetProcessDpiAwarenessFn = (PFN_SetProcessDpiAwareness)::GetProcAddress(shcore_dll, "SetProcessDpiAwareness")) + { + SetProcessDpiAwarenessFn(PROCESS_PER_MONITOR_DPI_AWARE); + return; + } + } +#if _WIN32_WINNT >= 0x0600 + ::SetProcessDPIAware(); +#endif +} + +#if defined(_MSC_VER) && !defined(NOGDI) +#pragma comment(lib, "gdi32") // Link with gdi32.lib for GetDeviceCaps(). MinGW will require linking with '-lgdi32' +#endif + +float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor) +{ + UINT xdpi = 96, ydpi = 96; + static BOOL bIsWindows8Point1OrGreater = IsWindows8Point1OrGreater(); + if (bIsWindows8Point1OrGreater) + { + static HINSTANCE shcore_dll = ::LoadLibraryA("shcore.dll"); // Reference counted per-process + if (PFN_GetDpiForMonitor GetDpiForMonitorFn = (PFN_GetDpiForMonitor)::GetProcAddress(shcore_dll, "GetDpiForMonitor")) + GetDpiForMonitorFn((HMONITOR)monitor, MDT_EFFECTIVE_DPI, &xdpi, &ydpi); + } +#ifndef NOGDI + else + { + const HDC dc = ::GetDC(NULL); + xdpi = ::GetDeviceCaps(dc, LOGPIXELSX); + ydpi = ::GetDeviceCaps(dc, LOGPIXELSY); + ::ReleaseDC(NULL, dc); + } +#endif + IM_ASSERT(xdpi == ydpi); // Please contact me if you hit this assert! + return xdpi / 96.0f; +} + +float ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd) +{ + HMONITOR monitor = ::MonitorFromWindow((HWND)hwnd, MONITOR_DEFAULTTONEAREST); + return ImGui_ImplWin32_GetDpiScaleForMonitor(monitor); +} + +//--------------------------------------------------------------------------------------------------------- +// Transparency related helpers (optional) +//-------------------------------------------------------------------------------------------------------- + +#if defined(_MSC_VER) +#pragma comment(lib, "dwmapi") // Link with dwmapi.lib. MinGW will require linking with '-ldwmapi' +#endif + +// [experimental] +// Borrowed from GLFW's function updateFramebufferTransparency() in src/win32_window.c +// (the Dwm* functions are Vista era functions but we are borrowing logic from GLFW) +void ImGui_ImplWin32_EnableAlphaCompositing(void* hwnd) +{ + if (!IsWindowsVistaOrGreater()) + return; + + BOOL composition; + if (FAILED(::DwmIsCompositionEnabled(&composition)) || !composition) + return; + + BOOL opaque; + DWORD color; + if (IsWindows8OrGreater() || (SUCCEEDED(::DwmGetColorizationColor(&color, &opaque)) && !opaque)) + { + HRGN region = ::CreateRectRgn(0, 0, -1, -1); + DWM_BLURBEHIND bb = {}; + bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; + bb.hRgnBlur = region; + bb.fEnable = TRUE; + ::DwmEnableBlurBehindWindow((HWND)hwnd, &bb); + ::DeleteObject(region); + } + else + { + DWM_BLURBEHIND bb = {}; + bb.dwFlags = DWM_BB_ENABLE; + ::DwmEnableBlurBehindWindow((HWND)hwnd, &bb); + } +} + +//--------------------------------------------------------------------------------------------------------- diff --git a/3rdparty/imgui/backend/imgui_impl_win32.h b/3rdparty/imgui/backend/imgui_impl_win32.h new file mode 100644 index 0000000..5197b7f --- /dev/null +++ b/3rdparty/imgui/backend/imgui_impl_win32.h @@ -0,0 +1,41 @@ +// dear imgui: Platform Backend for Windows (standard windows API for 32 and 64 bits applications) +// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..) + +// Implemented features: +// [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui) +// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. +// [X] Platform: Keyboard arrays indexed using VK_* Virtual Key Codes, e.g. ImGui::IsKeyPressed(VK_SPACE). +// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. + +// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. +// Read online: https://github.com/ocornut/imgui/tree/master/docs + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API + +IMGUI_IMPL_API bool ImGui_ImplWin32_Init(void* hwnd); +IMGUI_IMPL_API void ImGui_ImplWin32_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplWin32_NewFrame(); + +// Win32 message handler your application need to call. +// - Intentionally commented out in a '#if 0' block to avoid dragging dependencies on <windows.h> from this helper. +// - You should COPY the line below into your .cpp code to forward declare the function and then you can call it. +#if 0 +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); +#endif + +// DPI-related helpers (optional) +// - Use to enable DPI awareness without having to create an application manifest. +// - Your own app may already do this via a manifest or explicit calls. This is mostly useful for our examples/ apps. +// - In theory we could call simple functions from Windows SDK such as SetProcessDPIAware(), SetProcessDpiAwareness(), etc. +// but most of the functions provided by Microsoft require Windows 8.1/10+ SDK at compile time and Windows 8/10+ at runtime, +// neither we want to require the user to have. So we dynamically select and load those functions to avoid dependencies. +IMGUI_IMPL_API void ImGui_ImplWin32_EnableDpiAwareness(); +IMGUI_IMPL_API float ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd); // HWND hwnd +IMGUI_IMPL_API float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor); // HMONITOR monitor + +// Transparency related helpers (optional) [experimental] +// - Use to enable alpha compositing transparency with the desktop. +// - Use together with e.g. clearing your framebuffer with zero-alpha. +IMGUI_IMPL_API void ImGui_ImplWin32_EnableAlphaCompositing(void* hwnd); // HWND hwnd diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 320a5de..e8abe5b 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -24,13 +24,12 @@ endfunction() set(ENTRYPOINT_MODULE_SOURCES src/Entrypoint/main.cpp - src/Entrypoint/Common.cpp - src/Entrypoint/OpenGL2.cpp - src/Entrypoint/OpenGL3.cpp - src/Entrypoint/Vulkan.cpp - src/Entrypoint/DirectX11.cpp - src/Entrypoint/DirectX12.cpp - src/Entrypoint/Metal.mm + src/Entrypoint/Backend_OpenGL2.cpp + src/Entrypoint/Backend_OpenGL3.cpp + src/Entrypoint/Backend_Vulkan.cpp + src/Entrypoint/Backend_DirectX11.cpp + src/Entrypoint/Backend_DirectX12.cpp + src/Entrypoint/Backend_Metal.mm ) add_source_group(MODEL_MODULE_SOURCES @@ -61,12 +60,27 @@ add_source_group(UTILS_MODULE_SOURCES function(add_executable_variant TARGET_NAME) message("CpltCore: generating executable ${TARGET_NAME}") + if(BUILD_CORE_WITH_OPENGL2_BACKEND OR + BUILD_CORE_WITH_OPENGL3_BACKEND OR + BUILD_CORE_WITH_VULKAN_BACKEND OR + BUILD_CORE_WITH_METAL_BACKEND) + list(APPEND IMGUI_BACKEND_SOURCES + ${CMAKE_SOURCE_DIR}/3rdparty/imgui/backend/imgui_impl_glfw.cpp + ) + endif() + if(BUILD_CORE_WITH_DX11_BACKEND OR BUILD_CORE_WITH_DX12_BACKEND) + list(APPEND IMGUI_BACKEND_SOURCES + ${CMAKE_SOURCE_DIR}/3rdparty/imgui/backend/imgui_impl_win32.cpp + ) + endif() + add_executable(${TARGET_NAME} ${ENTRYPOINT_MODULE_SOURCES} ${MODEL_MODULE_SOURCES} ${UI_MODULE_SOURCES} ${UTILS_MODULE_SOURCES} ${UTILS_DIALOG_MODULE_SOURCES} + ${IMGUI_BACKEND_SOURCES} ) target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src @@ -95,12 +109,13 @@ function(add_executable_variant TARGET_NAME) BUILD_CORE_WITH_DX11_BACKEND=$<BOOL:${BUILD_CORE_WITH_DX11_BACKEND}> BUILD_CORE_WITH_DX12_BACKEND=$<BOOL:${BUILD_CORE_WITH_DX12_BACKEND}> ) - else() - target_compile_definitions(${TARGET_NAME} - PRIVATE - BUILD_CORE_WITH_DX11_BACKEND=0 - BUILD_CORE_WITH_DX12_BACKEND=0 - ) + + if(BUILD_CORE_WITH_DX11_BACKEND) + target_link_libraries(${TARGET_NAME} PRIVATE dxgi.lib d3d11.lib) + endif() + if(BUILD_CORE_WITH_DX12_BACKEND) + target_link_libraries(${TARGET_NAME} PRIVATE dxgi.lib d3d12.lib) + endif() endif() if(APPLE) @@ -109,11 +124,6 @@ function(add_executable_variant TARGET_NAME) PRIVATE BUILD_CORE_WITH_METAL_BACKEND=$<BOOL:${BUILD_CORE_WITH_METAL_BACKEND}> ) - else() - target_compile_definitions(${TARGET_NAME} - PRIVATE - BUILD_CORE_WITH_METAL_BACKEND=0 - ) endif() if(NOT APPLE) @@ -126,6 +136,13 @@ function(add_executable_variant TARGET_NAME) BUILD_CORE_WITH_OPENGL3_BACKEND=$<BOOL:${BUILD_CORE_WITH_OPENGL3_BACKEND}> BUILD_CORE_WITH_VULKAN_BACKEND=$<BOOL:${BUILD_CORE_WITH_VULKAN_BACKEND}> ) + + # TODO conditionally add opengl libraries + if(BUILD_CORE_WITH_VULKAN_BACKEND) + find_package(Vulkan REQUIRED FATAL_ERROR) + target_include_directories(${TARGET_NAME} PRIVATE ${Vulkan_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} PRIVATE ${Vulkan_LIBRARIES}) + endif() endif() # Platform specific dependencies for Utils/Dialog, not covered by conan diff --git a/core/src/Entrypoint/Backend.hpp b/core/src/Entrypoint/Backend.hpp new file mode 100644 index 0000000..9ceccb1 --- /dev/null +++ b/core/src/Entrypoint/Backend.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include <memory> + +class RenderingBackend { +public: + // Implemented in Backend_OpenGL2.cpp + static std::unique_ptr<RenderingBackend> CreateOpenGL2Backend(); + // Implemented in Backend_OpenGL3.cpp + static std::unique_ptr<RenderingBackend> CreateOpenGL3Backend(); + // Implemented in Backend_Vulkan.cpp + static std::unique_ptr<RenderingBackend> CreateVulkanBackend(); + // Implemented in Backend_DirectX11.cpp + static std::unique_ptr<RenderingBackend> CreateDx11Backend(); + // Implemented in Backend_DirectX12.cpp + static std::unique_ptr<RenderingBackend> CreateDx12Backend(); + // Implemented in Backend_Metal.cpp + static std::unique_ptr<RenderingBackend> CreateMetalBackend(); + + virtual ~RenderingBackend() = default; + virtual void RunUntilWindowClose(void (*windowContent)()) = 0; +}; diff --git a/core/src/Entrypoint/Backend_DirectX11.cpp b/core/src/Entrypoint/Backend_DirectX11.cpp new file mode 100644 index 0000000..8d46bf4 --- /dev/null +++ b/core/src/Entrypoint/Backend_DirectX11.cpp @@ -0,0 +1,238 @@ +#include "Backend.hpp" + +#if BUILD_CORE_WITH_DX11_BACKEND +# include <backend/imgui_impl_dx11.h> +# include <backend/imgui_impl_dx11.cpp> +# include <stdexcept> +# include <d3d11.h> +# include <tchar.h> +# include <backend/imgui_impl_win32.h> + +// Forward declare message handler from imgui_impl_win32.cpp +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +class DirectX11Backend : public RenderingBackend { +private: + HWND hWnd; + WNDCLASSEX wc; + + ID3D11Device* mD3dDevice = nullptr; + ID3D11DeviceContext* mD3dDeviceContext = nullptr; + IDXGISwapChain* mSwapChain = nullptr; + ID3D11RenderTargetView* mMainRenderTargetView = nullptr; + +public: + DirectX11Backend() { + ImGui_ImplWin32_EnableDpiAwareness(); + + wc.cbSize = sizeof(WNDCLASSEX); + wc.style = CS_CLASSDC; + wc.lpfnWndProc = &StaticWndProc; + wc.cbClsExtra = 0L; + wc.cbWndExtra = 0L; + wc.hInstance = GetModuleHandle(nullptr); + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + wc.lpszClassName = _T("Cplt"); + wc.hIconSm = nullptr; + ::RegisterClassEx(&wc); + + hWnd = ::CreateWindow( + wc.lpszClassName, + _T("Cplt main window"), + WS_OVERLAPPEDWINDOW, + /* x */ 100, + /* y */ 100, + /* window width */ 1280, + /* window height */ 800, + nullptr, + nullptr, + wc.hInstance, + this); + + if (!CreateDeviceD3D()) { + CleanupDeviceD3D(); + ::UnregisterClass(wc.lpszClassName, wc.hInstance); + throw std::runtime_error("Failed to create d3d device."); + } + + ::ShowWindow(hWnd, SW_SHOWDEFAULT); + ::UpdateWindow(hWnd); + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplWin32_Init(hWnd); + ImGui_ImplDX11_Init(mD3dDevice, mD3dDeviceContext); + } + + virtual ~DirectX11Backend() { + ImGui_ImplDX11_Shutdown(); + ImGui_ImplWin32_Shutdown(); + ImGui::DestroyContext(); + + CleanupDeviceD3D(); + ::DestroyWindow(hWnd); + ::UnregisterClass(wc.lpszClassName, wc.hInstance); + } + + virtual void RunUntilWindowClose(void (*windowContent)()) { + while (true) { + MSG msg; + bool done = false; + while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + if (msg.message == WM_QUIT) { + done = true; + } + } + if (done) break; + + ImGui_ImplDX11_NewFrame(); + ImGui_ImplWin32_NewFrame(); + ImGui::NewFrame(); + + windowContent(); + + ImGui::Render(); + const ImVec4 kClearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + const float kClearColorWithAlpha[4] = { kClearColor.x * kClearColor.w, kClearColor.y * kClearColor.w, kClearColor.z * kClearColor.w, kClearColor.w }; + mD3dDeviceContext->OMSetRenderTargets(1, &mMainRenderTargetView, nullptr); + mD3dDeviceContext->ClearRenderTargetView(mMainRenderTargetView, kClearColorWithAlpha); + ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); + + mSwapChain->Present(1, 0); // Present with vsync + } + } + +private: + bool CreateDeviceD3D() { + // Setup swap chain + DXGI_SWAP_CHAIN_DESC sd; + ZeroMemory(&sd, sizeof(sd)); + sd.BufferCount = 2; + sd.BufferDesc.Width = 0; + sd.BufferDesc.Height = 0; + sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + sd.BufferDesc.RefreshRate.Numerator = 60; + sd.BufferDesc.RefreshRate.Denominator = 1; + sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + sd.OutputWindow = hWnd; + sd.SampleDesc.Count = 1; + sd.SampleDesc.Quality = 0; + sd.Windowed = TRUE; + sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + + UINT createDeviceFlags = 0; + //createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; + D3D_FEATURE_LEVEL featureLevel; + const D3D_FEATURE_LEVEL featureLevelArray[2] = { + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_0, + }; + if (D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &mSwapChain, &mD3dDevice, &featureLevel, &mD3dDeviceContext) != S_OK) { + return false; + } + + CreateRenderTarget(); + return true; + } + + void CleanupDeviceD3D() { + CleanupRenderTarget(); + if (mSwapChain) { + mSwapChain->Release(); + mSwapChain = nullptr; + } + if (mD3dDeviceContext) { + mD3dDeviceContext->Release(); + mD3dDeviceContext = nullptr; + } + if (mD3dDevice) { + mD3dDevice->Release(); + mD3dDevice = nullptr; + } + } + + void CreateRenderTarget() { + ID3D11Texture2D* pBackBuffer; + mSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); + mD3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &mMainRenderTargetView); + pBackBuffer->Release(); + } + + void CleanupRenderTarget() { + if (mMainRenderTargetView) { + mMainRenderTargetView->Release(); + mMainRenderTargetView = nullptr; + } + } + + static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + DirectX11Backend* self; + if (uMsg == WM_NCCREATE) { + auto lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam); + self = static_cast<DirectX11Backend*>(lpcs->lpCreateParams); + self->hWnd = hWnd; + SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(self)); + } else { + self = reinterpret_cast<DirectX11Backend*>(GetWindowLongPtr(hWnd, GWLP_USERDATA)); + } + + if (self) { + return self->WndProc(uMsg, wParam, lParam); + } else { + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + } + + LRESULT WndProc(UINT msg, WPARAM wParam, LPARAM lParam) { + if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) { + return true; + } + + switch (msg) { + case WM_SIZE: { + if (mD3dDevice != nullptr && wParam != SIZE_MINIMIZED) { + CleanupRenderTarget(); + mSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, 0); + CreateRenderTarget(); + } + return 0; + } + + case WM_SYSCOMMAND: { + // Disable ALT application menu + if ((wParam & 0xfff0) == SC_KEYMENU) { + return 0; + } + } break; + + case WM_DESTROY: { + ::PostQuitMessage(0); + return 0; + } + } + return ::DefWindowProc(hWnd, msg, wParam, lParam); + } +}; + +std::unique_ptr<RenderingBackend> RenderingBackend::CreateDx11Backend() { + try { + return std::make_unique<DirectX11Backend>(); + } catch (std::exception& e) { + return nullptr; + } +} + +#else // ^^ BUILD_CORE_WITH_DX11_BACKEND | BUILD_CORE_WITH_DX11_BACKEND vv + +std::unique_ptr<RenderingBackend> RenderingBackend::CreateDx11Backend() { + return nullptr; +} + +#endif diff --git a/core/src/Entrypoint/Backend_DirectX12.cpp b/core/src/Entrypoint/Backend_DirectX12.cpp new file mode 100644 index 0000000..c0492c2 --- /dev/null +++ b/core/src/Entrypoint/Backend_DirectX12.cpp @@ -0,0 +1,454 @@ +#include "Backend.hpp" + +#if BUILD_CORE_WITH_DX12_BACKEND +# include <backend/imgui_impl_dx12.h> +# include <backend/imgui_impl_win32.h> +# include <d3d12.h> +# include <dxgi1_4.h> +# include <tchar.h> +# include <backend/imgui_impl_dx12.cpp> +# include <stdexcept> + +constexpr int kNumFramesInFlight = 3; +constexpr int kNumBackBuffers = 3; + +// Forward declare message handler from imgui_impl_win32.cpp +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +class DirectX12Backend : public RenderingBackend { +private: + struct FrameContext { + ID3D12CommandAllocator* CommandAllocator; + UINT64 FenceValue; + }; + + HWND hWnd; + WNDCLASSEX wc; + + FrameContext mFrameContext[kNumFramesInFlight] = {}; + UINT mFrameIndex = 0; + + ID3D12Device* mD3dDevice = nullptr; + ID3D12DescriptorHeap* mD3dRtvDescHeap = nullptr; + ID3D12DescriptorHeap* mD3dSrvDescHeap = nullptr; + ID3D12CommandQueue* mD3dCommandQueue = nullptr; + ID3D12GraphicsCommandList* mD3dCommandList = nullptr; + ID3D12Fence* mFence = nullptr; + HANDLE mFenceEvent = nullptr; + UINT64 mFenceLastSignaledValue = 0; + IDXGISwapChain3* mSwapChain = nullptr; + HANDLE mSwapChainWaitableObject = nullptr; + ID3D12Resource* mMainRenderTargetResource[kNumBackBuffers] = {}; + D3D12_CPU_DESCRIPTOR_HANDLE mMainRenderTargetDescriptor[kNumBackBuffers] = {}; + +public: + DirectX12Backend() { + ImGui_ImplWin32_EnableDpiAwareness(); + + wc.cbSize = sizeof(WNDCLASSEX); + wc.style = CS_CLASSDC; + wc.lpfnWndProc = &StaticWndProc; + wc.cbClsExtra = 0L; + wc.cbWndExtra = 0L; + wc.hInstance = GetModuleHandle(nullptr); + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + wc.lpszClassName = _T("Cplt"); + wc.hIconSm = nullptr; + ::RegisterClassEx(&wc); + + hWnd = ::CreateWindow( + wc.lpszClassName, + _T("Cplt main window"), + WS_OVERLAPPEDWINDOW, + /* x */ 100, + /* y */ 100, + /* window width */ 1280, + /* window height */ 800, + nullptr, + nullptr, + wc.hInstance, + this); + + if (!CreateDeviceD3D()) { + CleanupDeviceD3D(); + ::UnregisterClass(wc.lpszClassName, wc.hInstance); + throw std::runtime_error("Failed to create d3d device."); + } + + ::ShowWindow(hWnd, SW_SHOWDEFAULT); + ::UpdateWindow(hWnd); + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplWin32_Init(hWnd); + ImGui_ImplDX12_Init(mD3dDevice, kNumFramesInFlight, DXGI_FORMAT_R8G8B8A8_UNORM, mD3dSrvDescHeap, mD3dSrvDescHeap->GetCPUDescriptorHandleForHeapStart(), mD3dSrvDescHeap->GetGPUDescriptorHandleForHeapStart()); + } + + virtual ~DirectX12Backend() { + WaitForLastSubmittedFrame(); + + // Cleanup + ImGui_ImplDX12_Shutdown(); + ImGui_ImplWin32_Shutdown(); + ImGui::DestroyContext(); + + CleanupDeviceD3D(); + ::DestroyWindow(hWnd); + ::UnregisterClass(wc.lpszClassName, wc.hInstance); + } + + virtual void RunUntilWindowClose(void (*windowContent)()) { + while (true) { + MSG msg; + bool done = false; + while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) + { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + if (msg.message == WM_QUIT) { + done = true; + } + } + if (done) break; + + // Start the Dear ImGui frame + ImGui_ImplDX12_NewFrame(); + ImGui_ImplWin32_NewFrame(); + ImGui::NewFrame(); + + windowContent(); + + ImGui::Render(); + + FrameContext* frameCtx = WaitForNextFrameResources(); + UINT backBufferIdx = mSwapChain->GetCurrentBackBufferIndex(); + frameCtx->CommandAllocator->Reset(); + + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = mMainRenderTargetResource[backBufferIdx]; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; + mD3dCommandList->Reset(frameCtx->CommandAllocator, nullptr); + mD3dCommandList->ResourceBarrier(1, &barrier); + + const ImVec4 kClearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + const float kClearColorWithAlpha[4] = { kClearColor.x * kClearColor.w, kClearColor.y * kClearColor.w, kClearColor.z * kClearColor.w, kClearColor.w }; + mD3dCommandList->ClearRenderTargetView(mMainRenderTargetDescriptor[backBufferIdx], kClearColorWithAlpha, 0, nullptr); + mD3dCommandList->OMSetRenderTargets(1, &mMainRenderTargetDescriptor[backBufferIdx], FALSE, nullptr); + mD3dCommandList->SetDescriptorHeaps(1, &mD3dSrvDescHeap); + ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), mD3dCommandList); + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; + mD3dCommandList->ResourceBarrier(1, &barrier); + mD3dCommandList->Close(); + + mD3dCommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&mD3dCommandList); + + mSwapChain->Present(1, 0); // Present with vsync + + UINT64 fenceValue = mFenceLastSignaledValue + 1; + mD3dCommandQueue->Signal(mFence, fenceValue); + mFenceLastSignaledValue = fenceValue; + frameCtx->FenceValue = fenceValue; + } + } + +private: + bool CreateDeviceD3D() { + // Setup swap chain + DXGI_SWAP_CHAIN_DESC1 sd; + { + ZeroMemory(&sd, sizeof(sd)); + sd.BufferCount = kNumBackBuffers; + sd.Width = 0; + sd.Height = 0; + sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + sd.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; + sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + sd.SampleDesc.Count = 1; + sd.SampleDesc.Quality = 0; + sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + sd.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; + sd.Scaling = DXGI_SCALING_STRETCH; + sd.Stereo = FALSE; + } + + // Create device + D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0; + if (D3D12CreateDevice(nullptr, featureLevel, IID_PPV_ARGS(&mD3dDevice)) != S_OK) { + return false; + } + + { + D3D12_DESCRIPTOR_HEAP_DESC desc = {}; + desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + desc.NumDescriptors = kNumBackBuffers; + desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + desc.NodeMask = 1; + if (mD3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&mD3dRtvDescHeap)) != S_OK) { + return false; + } + + SIZE_T rtvDescriptorSize = mD3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = mD3dRtvDescHeap->GetCPUDescriptorHandleForHeapStart(); + for (UINT i = 0; i < kNumBackBuffers; i++) { + mMainRenderTargetDescriptor[i] = rtvHandle; + rtvHandle.ptr += rtvDescriptorSize; + } + } + + { + D3D12_DESCRIPTOR_HEAP_DESC desc = {}; + desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + desc.NumDescriptors = 1; + desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + if (mD3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&mD3dSrvDescHeap)) != S_OK) { + return false; + } + } + + { + D3D12_COMMAND_QUEUE_DESC desc = {}; + desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + desc.NodeMask = 1; + if (mD3dDevice->CreateCommandQueue(&desc, IID_PPV_ARGS(&mD3dCommandQueue)) != S_OK) { + return false; + } + } + + for (UINT i = 0; i < kNumFramesInFlight; i++) { + if (mD3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&mFrameContext[i].CommandAllocator)) != S_OK) { + return false; + } + } + + if (mD3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mFrameContext[0].CommandAllocator, nullptr, IID_PPV_ARGS(&mD3dCommandList)) != S_OK || + mD3dCommandList->Close() != S_OK) + { + return false; + } + + if (mD3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)) != S_OK) return false; + + mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (mFenceEvent == nullptr) return false; + + { + IDXGIFactory4* dxgiFactory = nullptr; + IDXGISwapChain1* swapChain1 = nullptr; + if (CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)) != S_OK) + return false; + if (dxgiFactory->CreateSwapChainForHwnd(mD3dCommandQueue, hWnd, &sd, nullptr, nullptr, &swapChain1) != S_OK) + return false; + if (swapChain1->QueryInterface(IID_PPV_ARGS(&mSwapChain)) != S_OK) + return false; + swapChain1->Release(); + dxgiFactory->Release(); + mSwapChain->SetMaximumFrameLatency(kNumBackBuffers); + mSwapChainWaitableObject = mSwapChain->GetFrameLatencyWaitableObject(); + } + + CreateRenderTarget(); + return true; + } + + void CleanupDeviceD3D() { + CleanupRenderTarget(); + if (mSwapChain) { + mSwapChain->Release(); + mSwapChain = nullptr; + } + if (mSwapChainWaitableObject != nullptr) { + CloseHandle(mSwapChainWaitableObject); + } + for (UINT i = 0; i < kNumFramesInFlight; i++) + if (mFrameContext[i].CommandAllocator) { + mFrameContext[i].CommandAllocator->Release(); + mFrameContext[i].CommandAllocator = nullptr; + } + if (mD3dCommandQueue) { + mD3dCommandQueue->Release(); + mD3dCommandQueue = nullptr; + } + if (mD3dCommandList) { + mD3dCommandList->Release(); + mD3dCommandList = nullptr; + } + if (mD3dRtvDescHeap) { + mD3dRtvDescHeap->Release(); + mD3dRtvDescHeap = nullptr; + } + if (mD3dSrvDescHeap) { + mD3dSrvDescHeap->Release(); + mD3dSrvDescHeap = nullptr; + } + if (mFence) { + mFence->Release(); + mFence = nullptr; + } + if (mFenceEvent) { + CloseHandle(mFenceEvent); + mFenceEvent = nullptr; + } + if (mD3dDevice) { + mD3dDevice->Release(); + mD3dDevice = nullptr; + } + } + + void CreateRenderTarget() { + for (UINT i = 0; i < kNumBackBuffers; i++) + { + ID3D12Resource* pBackBuffer = nullptr; + mSwapChain->GetBuffer(i, IID_PPV_ARGS(&pBackBuffer)); + mD3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, mMainRenderTargetDescriptor[i]); + mMainRenderTargetResource[i] = pBackBuffer; + } + } + + void CleanupRenderTarget() { + WaitForLastSubmittedFrame(); + + for (UINT i = 0; i < kNumBackBuffers; i++) + if (mMainRenderTargetResource[i]) { + mMainRenderTargetResource[i]->Release(); + mMainRenderTargetResource[i] = nullptr; + } + } + + void WaitForLastSubmittedFrame() { + FrameContext* frameCtx = &mFrameContext[mFrameIndex % kNumFramesInFlight]; + + UINT64 fenceValue = frameCtx->FenceValue; + if (fenceValue == 0) + return; // No fence was signaled + + frameCtx->FenceValue = 0; + if (mFence->GetCompletedValue() >= fenceValue) + return; + + mFence->SetEventOnCompletion(fenceValue, mFenceEvent); + WaitForSingleObject(mFenceEvent, INFINITE); + } + + FrameContext* WaitForNextFrameResources() { + UINT nextFrameIndex = mFrameIndex + 1; + mFrameIndex = nextFrameIndex; + + HANDLE waitableObjects[] = { mSwapChainWaitableObject, nullptr }; + DWORD numWaitableObjects = 1; + + FrameContext* frameCtx = &mFrameContext[nextFrameIndex % kNumFramesInFlight]; + UINT64 fenceValue = frameCtx->FenceValue; + if (fenceValue != 0) // means no fence was signaled + { + frameCtx->FenceValue = 0; + mFence->SetEventOnCompletion(fenceValue, mFenceEvent); + waitableObjects[1] = mFenceEvent; + numWaitableObjects = 2; + } + + WaitForMultipleObjects(numWaitableObjects, waitableObjects, TRUE, INFINITE); + + return frameCtx; + } + + void ResizeSwapChain(int width, int height) { + DXGI_SWAP_CHAIN_DESC1 sd; + mSwapChain->GetDesc1(&sd); + sd.Width = width; + sd.Height = height; + + IDXGIFactory4* dxgiFactory = nullptr; + mSwapChain->GetParent(IID_PPV_ARGS(&dxgiFactory)); + + mSwapChain->Release(); + CloseHandle(mSwapChainWaitableObject); + + IDXGISwapChain1* swapChain1 = nullptr; + dxgiFactory->CreateSwapChainForHwnd(mD3dCommandQueue, hWnd, &sd, nullptr, nullptr, &swapChain1); + swapChain1->QueryInterface(IID_PPV_ARGS(&mSwapChain)); + swapChain1->Release(); + dxgiFactory->Release(); + + mSwapChain->SetMaximumFrameLatency(kNumBackBuffers); + + mSwapChainWaitableObject = mSwapChain->GetFrameLatencyWaitableObject(); + assert(mSwapChainWaitableObject != nullptr); + } + + static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + DirectX12Backend* self; + if (uMsg == WM_NCCREATE) { + auto lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam); + self = static_cast<DirectX12Backend*>(lpcs->lpCreateParams); + self->hWnd = hWnd; + SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(self)); + } else { + self = reinterpret_cast<DirectX12Backend*>(GetWindowLongPtr(hWnd, GWLP_USERDATA)); + } + + if (self) { + return self->WndProc(uMsg, wParam, lParam); + } else { + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + } + + LRESULT WndProc(UINT msg, WPARAM wParam, LPARAM lParam) { + if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) { + return true; + } + + switch (msg) { + case WM_SIZE: { + if (mD3dDevice != nullptr && wParam != SIZE_MINIMIZED) { + WaitForLastSubmittedFrame(); + ImGui_ImplDX12_InvalidateDeviceObjects(); + CleanupRenderTarget(); + ResizeSwapChain((UINT)LOWORD(lParam), (UINT)HIWORD(lParam)); + CreateRenderTarget(); + ImGui_ImplDX12_CreateDeviceObjects(); + } + return 0; + } + + case WM_SYSCOMMAND: { + // Disable ALT application menu + if ((wParam & 0xfff0) == SC_KEYMENU) { + return 0; + } + } break; + + case WM_DESTROY: { + ::PostQuitMessage(0); + return 0; + } + } + return ::DefWindowProc(hWnd, msg, wParam, lParam); + } +}; + +std::unique_ptr<RenderingBackend> RenderingBackend::CreateDx12Backend() { + try { + return std::make_unique<DirectX12Backend>(); + } catch (std::exception& e) { + return nullptr; + } +} + +#else // ^^ BUILD_CORE_WITH_DX12_BACKEND | BUILD_CORE_WITH_DX12_BACKEND vv + +std::unique_ptr<RenderingBackend> RenderingBackend::CreateDx12Backend() { + return nullptr; +} + +#endif diff --git a/core/src/Entrypoint/Backend_Metal.mm b/core/src/Entrypoint/Backend_Metal.mm new file mode 100644 index 0000000..a1f4993 --- /dev/null +++ b/core/src/Entrypoint/Backend_Metal.mm @@ -0,0 +1,34 @@ +#include "Backend.hpp" + +#if BUILD_CORE_WITH_METAL_BACKEND + +class MetalBackend : public RenderingBackend { +public: + MetalBackend() { + // TODO + } + + virtual ~MetalBackend() { + // TODO + } + + virtual void RunUntilWindowClose(void (*windowContent)()) { + // TODO + } +}; + +std::unique_ptr<RenderingBackend> RenderingBackend::CreateMetalBackend() { + try { + return std::make_unique<MetalBackend>(); + } catch (std::exception& e) { + return nullptr; + } +} + +#else // ^^ BUILD_CORE_WITH_METAL_BACKEND | BUILD_CORE_WITH_METAL_BACKEND vv + +std::unique_ptr<RenderingBackend> RenderingBackend::CreateMetalBackend() { + return nullptr; +} + +#endif diff --git a/core/src/Entrypoint/Backend_OpenGL2.cpp b/core/src/Entrypoint/Backend_OpenGL2.cpp new file mode 100644 index 0000000..422ad91 --- /dev/null +++ b/core/src/Entrypoint/Backend_OpenGL2.cpp @@ -0,0 +1,98 @@ +#include "Entrypoint/Backend.hpp" + +#if BUILD_CORE_WITH_OPENGL2_BACKEND +# include <glad/glad.h> +# include <GLFW/glfw3.h> +# include <backend/imgui_impl_glfw.h> +# include <backend/imgui_impl_opengl2.h> +# include <imgui.h> +# include <stdexcept> +# include <iostream> + +# define IMGUI_IMPL_OPENGL_LOADER_GLAD +# include <backend/imgui_impl_opengl2.cpp> + +class OpenGL2Backend : public RenderingBackend { +private: + GLFWwindow* mWindow; + +public: + OpenGL2Backend() { + glfwSetErrorCallback(&GlfwErrorCallback); + if (!glfwInit()) { + throw std::runtime_error("Failed to initialize GLFW."); + } + + mWindow = glfwCreateWindow(1280, 720, "Cplt", nullptr, nullptr); + if (mWindow == nullptr) { + throw std::runtime_error("Failed to create GLFW window."); + } + glfwMakeContextCurrent(mWindow); + glfwSwapInterval(1); // Enable vsync + + if (gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) == 0) { + throw std::runtime_error("Failed to initialize OpenGL."); + } + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplGlfw_InitForOpenGL(mWindow, true); + ImGui_ImplOpenGL2_Init(); + } + + virtual ~OpenGL2Backend() { + ImGui_ImplOpenGL2_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + glfwDestroyWindow(mWindow); + glfwTerminate(); + } + + virtual void RunUntilWindowClose(void (*windowContent)()) { + while (!glfwWindowShouldClose(mWindow)) { + glfwPollEvents(); + + ImGui_ImplOpenGL2_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + windowContent(); + + int displayWidth, displayHeight; + glfwGetFramebufferSize(mWindow, &displayWidth, &displayHeight); + glViewport(0, 0, displayWidth, displayHeight); + + const ImVec4 kClearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + glClearColor(kClearColor.x * kClearColor.w, kClearColor.y * kClearColor.w, kClearColor.z * kClearColor.w, kClearColor.w); + glClear(GL_COLOR_BUFFER_BIT); + + ImGui::Render(); + ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); + + glfwMakeContextCurrent(mWindow); + glfwSwapBuffers(mWindow); + } + } + + static void GlfwErrorCallback(int errorCode, const char* message) { + std::cerr << "GLFW Error " << errorCode << ": " << message << "\n"; + } +}; + +std::unique_ptr<RenderingBackend> RenderingBackend::CreateOpenGL2Backend() { + try { + return std::make_unique<OpenGL2Backend>(); + } catch (std::exception& e) { + return nullptr; + } +} + +#else // ^^ BUILD_CORE_WITH_OPENGL2_BACKEND | !BUILD_CORE_WITH_OPENGL2_BACKEND vv + +std::unique_ptr<RenderingBackend> RenderingBackend::CreateOpenGL2Backend(){}() { + return nullptr; +} + +#endif diff --git a/core/src/Entrypoint/Backend_OpenGL3.cpp b/core/src/Entrypoint/Backend_OpenGL3.cpp new file mode 100644 index 0000000..36a0cb3 --- /dev/null +++ b/core/src/Entrypoint/Backend_OpenGL3.cpp @@ -0,0 +1,113 @@ +#include "Entrypoint/Backend.hpp" + +#if BUILD_CORE_WITH_OPENGL3_BACKEND +# include <glad/glad.h> +# include <GLFW/glfw3.h> +# include <iostream> +# include <backend/imgui_impl_glfw.h> +# include <backend/imgui_impl_opengl3.h> +# include <imgui.h> +# include <stdexcept> + +# define IMGUI_IMPL_OPENGL_LOADER_GLAD +# include <backend/imgui_impl_opengl3.cpp> + +class OpenGL3Backend : public RenderingBackend { +private: + GLFWwindow* mWindow; + +public: + OpenGL3Backend() { + glfwSetErrorCallback(&GlfwErrorCallback); + if (!glfwInit()) { + throw std::runtime_error("Failed to initialize GLFW."); + } + +# if PLATFORM_APPLE + // GL 3.2 + GLSL 150 + const char* glslVersion = "#version 150"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac +# else + // GL 3.0 + GLSL 130 + const char* glslVersion = "#version 130"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only + //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only +# endif + + mWindow = glfwCreateWindow(1280, 720, "Cplt", nullptr, nullptr); + if (mWindow == nullptr) { + throw std::runtime_error("Failed to create GLFW window."); + } + glfwMakeContextCurrent(mWindow); + glfwSwapInterval(1); // Enable vsync + + if (gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) == 0) { + throw std::runtime_error("Failed to initialize OpenGL."); + } + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplGlfw_InitForOpenGL(mWindow, true); + ImGui_ImplOpenGL3_Init(glslVersion); + } + + virtual ~OpenGL3Backend() { + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + glfwDestroyWindow(mWindow); + glfwTerminate(); + } + + virtual void RunUntilWindowClose(void (*windowContent)()) { + while (!glfwWindowShouldClose(mWindow)) { + glfwPollEvents(); + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + windowContent(); + + int displayWidth, displayHeight; + glfwGetFramebufferSize(mWindow, &displayWidth, &displayHeight); + glViewport(0, 0, displayWidth, displayHeight); + + const ImVec4 kClearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + glClearColor(kClearColor.x * kClearColor.w, kClearColor.y * kClearColor.w, kClearColor.z * kClearColor.w, kClearColor.w); + glClear(GL_COLOR_BUFFER_BIT); + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + glfwSwapBuffers(mWindow); + } + } + + static void GlfwErrorCallback(int errorCode, const char* message) { + std::cerr << "GLFW Error " << errorCode << ": " << message << "\n"; + } +}; + +std::unique_ptr<RenderingBackend> RenderingBackend::CreateOpenGL3Backend() { + try { + return std::make_unique<OpenGL3Backend>(); + } catch (std::exception& e) { + return nullptr; + } +} + +#else // ^^ BUILD_CORE_WITH_OPENGL3_BACKEND | !BUILD_CORE_WITH_OPENGL3_BACKEND vv + +std::unique_ptr<RenderingBackend> RenderingBackend::CreateOpenGL3Backend() { + return nullptr; +} + +#endif diff --git a/core/src/Entrypoint/Backend_Vulkan.cpp b/core/src/Entrypoint/Backend_Vulkan.cpp new file mode 100644 index 0000000..9d79acf --- /dev/null +++ b/core/src/Entrypoint/Backend_Vulkan.cpp @@ -0,0 +1,424 @@ +#include "Entrypoint/Backend.hpp" + +#if BUILD_CORE_WITH_VULKAN_BACKEND +# include <iostream> +# include <stdexcept> + +# define GLFW_INCLUDE_NONE +# define GLFW_INCLUDE_VULKAN +# include <GLFW/glfw3.h> + +# include <backend/imgui_impl_glfw.h> +# include <backend/imgui_impl_vulkan.h> +# include <backend/imgui_impl_vulkan.cpp> + +class VulkanBackend : public RenderingBackend { +private: + GLFWwindow* mWindow; + + VkAllocationCallbacks* mAllocator = NULL; + VkInstance mInstance = VK_NULL_HANDLE; + VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE; + VkDevice mDevice = VK_NULL_HANDLE; + uint32_t mQueueFamily = (uint32_t)-1; + VkQueue mQueue = VK_NULL_HANDLE; + VkDebugReportCallbackEXT mDebugReport = VK_NULL_HANDLE; + VkPipelineCache mPipelineCache = VK_NULL_HANDLE; + VkDescriptorPool mDescriptorPool = VK_NULL_HANDLE; + + ImGui_ImplVulkanH_Window mMainWindowData; + int mMinImageCount = 2; + bool mSwapChainRebuild = false; + +public: + VulkanBackend() { + glfwSetErrorCallback(&GlfwErrorCallback); + if (!glfwInit()) { + throw std::runtime_error("Failed to initialize GLFW."); + } + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + mWindow = glfwCreateWindow(1280, 720, "Cplt", nullptr, nullptr); + if (mWindow == nullptr) { + throw std::runtime_error("Failed to create GLFW window."); + } + + if (!glfwVulkanSupported()) { + throw std::runtime_error("GLFW reports vulkan not supported."); + } + + uint32_t extensionsCount = 0; + const char** extensions = glfwGetRequiredInstanceExtensions(&extensionsCount); + SetupVulkan(extensions, extensionsCount); + + // Create window surface + VkSurfaceKHR surface; + VkResult err = glfwCreateWindowSurface(mInstance, mWindow, mAllocator, &surface); + CheckVkResults(err); + + // Create framebuffers + int w, h; + glfwGetFramebufferSize(mWindow, &w, &h); + SetupVulkanWindow(&mMainWindowData, surface, w, h); + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplGlfw_InitForVulkan(mWindow, true); + ImGui_ImplVulkan_InitInfo init_info = {}; + init_info.Instance = mInstance; + init_info.PhysicalDevice = mPhysicalDevice; + init_info.Device = mDevice; + init_info.QueueFamily = mQueueFamily; + init_info.Queue = mQueue; + init_info.PipelineCache = mPipelineCache; + init_info.DescriptorPool = mDescriptorPool; + init_info.Allocator = mAllocator; + init_info.MinImageCount = mMinImageCount; + init_info.ImageCount = mMainWindowData.ImageCount; + init_info.CheckVkResultFn = CheckVkResults; + ImGui_ImplVulkan_Init(&init_info, mMainWindowData.RenderPass); + } + + virtual ~VulkanBackend() { + auto err = vkDeviceWaitIdle(mDevice); + CheckVkResults(err); + ImGui_ImplVulkan_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + CleanupVulkanWindow(); + CleanupVulkan(); + + glfwDestroyWindow(mWindow); + glfwTerminate(); + } + + virtual void RunUntilWindowClose(void (*windowContent)()) override { + // Upload Fonts + { + // Use any command queue + VkCommandPool commandPool = mMainWindowData.Frames[mMainWindowData.FrameIndex].CommandPool; + VkCommandBuffer commandBuffer = mMainWindowData.Frames[mMainWindowData.FrameIndex].CommandBuffer; + + CheckVkResults(vkResetCommandPool(mDevice, commandPool, 0)); + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + CheckVkResults(vkBeginCommandBuffer(commandBuffer, &beginInfo)); + + ImGui_ImplVulkan_CreateFontsTexture(commandBuffer); + + VkSubmitInfo endInfo = {}; + endInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + endInfo.commandBufferCount = 1; + endInfo.pCommandBuffers = &commandBuffer; + CheckVkResults(vkEndCommandBuffer(commandBuffer)); + CheckVkResults(vkQueueSubmit(mQueue, 1, &endInfo, VK_NULL_HANDLE)); + + CheckVkResults(vkDeviceWaitIdle(mDevice)); + ImGui_ImplVulkan_DestroyFontUploadObjects(); + } + + while (!glfwWindowShouldClose(mWindow)) { + glfwPollEvents(); + + // Resize swap chain? + if (mSwapChainRebuild) { + int width, height; + glfwGetFramebufferSize(mWindow, &width, &height); + if (width > 0 && height > 0) { + ImGui_ImplVulkan_SetMinImageCount(mMinImageCount); + ImGui_ImplVulkanH_CreateOrResizeWindow(mInstance, mPhysicalDevice, mDevice, &mMainWindowData, mQueueFamily, mAllocator, width, height, mMinImageCount); + mMainWindowData.FrameIndex = 0; + mSwapChainRebuild = false; + } + } + + // Start the Dear ImGui frame + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + windowContent(); + + ImGui::Render(); + ImDrawData* drawData = ImGui::GetDrawData(); + const bool isMinimized = (drawData->DisplaySize.x <= 0.0f || drawData->DisplaySize.y <= 0.0f); + if (!isMinimized) { + const ImVec4 kClearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + mMainWindowData.ClearValue.color.float32[0] = kClearColor.x * kClearColor.w; + mMainWindowData.ClearValue.color.float32[1] = kClearColor.y * kClearColor.w; + mMainWindowData.ClearValue.color.float32[2] = kClearColor.z * kClearColor.w; + mMainWindowData.ClearValue.color.float32[3] = kClearColor.w; + FrameRender(&mMainWindowData, drawData); + FramePresent(&mMainWindowData); + } + } + } + +private: + void SetupVulkan(const char** extensions, uint32_t extensions_count) { + VkResult err; + + // Create Vulkan Instance + { + VkInstanceCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.enabledExtensionCount = extensions_count; + createInfo.ppEnabledExtensionNames = extensions; + // Create Vulkan Instance without any debug feature + err = vkCreateInstance(&createInfo, mAllocator, &mInstance); + CheckVkResults(err); + } + + // Select GPU + { + uint32_t gpuCount; + err = vkEnumeratePhysicalDevices(mInstance, &gpuCount, NULL); + CheckVkResults(err); + IM_ASSERT(gpuCount > 0); + + VkPhysicalDevice* gpus = (VkPhysicalDevice*)malloc(sizeof(VkPhysicalDevice) * gpuCount); + err = vkEnumeratePhysicalDevices(mInstance, &gpuCount, gpus); + CheckVkResults(err); + + // If a number >1 of GPUs got reported, find discrete GPU if present, or use first one available. This covers + // most common cases (multi-gpu/integrated+dedicated graphics). Handling more complicated setups (multiple + // dedicated GPUs) is out of scope of this sample. + int useGpu = 0; + for (int i = 0; i < (int)gpuCount; i++) + { + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(gpus[i], &properties); + if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) + { + useGpu = i; + break; + } + } + + mPhysicalDevice = gpus[useGpu]; + free(gpus); + } + + // Select graphics queue family + { + uint32_t count; + vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &count, NULL); + + auto queues = std::make_unique<VkQueueFamilyProperties[]>(count); + vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &count, queues.get()); + for (uint32_t i = 0; i < count; i++) { + if (queues[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) + { + mQueueFamily = i; + break; + } + } + + IM_ASSERT(mQueueFamily != (uint32_t)-1); + } + + // Create Logical Device (with 1 queue) + { + int deviceExtensionCount = 1; + const char* deviceExtensions[] = { "VK_KHR_swapchain" }; + const float queuePriority[] = { 1.0f }; + VkDeviceQueueCreateInfo queue_info[1] = {}; + queue_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_info[0].queueFamilyIndex = mQueueFamily; + queue_info[0].queueCount = 1; + queue_info[0].pQueuePriorities = queuePriority; + VkDeviceCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = sizeof(queue_info) / sizeof(queue_info[0]); + createInfo.pQueueCreateInfos = queue_info; + createInfo.enabledExtensionCount = deviceExtensionCount; + createInfo.ppEnabledExtensionNames = deviceExtensions; + err = vkCreateDevice(mPhysicalDevice, &createInfo, mAllocator, &mDevice); + CheckVkResults(err); + vkGetDeviceQueue(mDevice, mQueueFamily, 0, &mQueue); + } + + // Create Descriptor Pool + { + VkDescriptorPoolSize poolSizes[] = { + { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 }, + { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 }, + { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 } + }; + VkDescriptorPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + poolInfo.maxSets = 1000 * IM_ARRAYSIZE(poolSizes); + poolInfo.poolSizeCount = (uint32_t)IM_ARRAYSIZE(poolSizes); + poolInfo.pPoolSizes = poolSizes; + err = vkCreateDescriptorPool(mDevice, &poolInfo, mAllocator, &mDescriptorPool); + CheckVkResults(err); + } + } + + void SetupVulkanWindow(ImGui_ImplVulkanH_Window* wd, VkSurfaceKHR surface, int width, int height) { + wd->Surface = surface; + + // Check for WSI support + VkBool32 res; + vkGetPhysicalDeviceSurfaceSupportKHR(mPhysicalDevice, mQueueFamily, wd->Surface, &res); + if (res != VK_TRUE) { + throw "Error no WSI support on physical device 0."; + } + + // Select Surface Format + const VkFormat requestSurfaceImageFormat[] = { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM }; + const VkColorSpaceKHR requestSurfaceColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; + wd->SurfaceFormat = ImGui_ImplVulkanH_SelectSurfaceFormat(mPhysicalDevice, wd->Surface, requestSurfaceImageFormat, (size_t)IM_ARRAYSIZE(requestSurfaceImageFormat), requestSurfaceColorSpace); + + // Select Present Mode + VkPresentModeKHR present_modes[] = { VK_PRESENT_MODE_FIFO_KHR }; + wd->PresentMode = ImGui_ImplVulkanH_SelectPresentMode(mPhysicalDevice, wd->Surface, &present_modes[0], IM_ARRAYSIZE(present_modes)); + + // Create SwapChain, RenderPass, Framebuffer, etc. + IM_ASSERT(mMinImageCount >= 2); + ImGui_ImplVulkanH_CreateOrResizeWindow(mInstance, mPhysicalDevice, mDevice, wd, mQueueFamily, mAllocator, width, height, mMinImageCount); + } + + void FrameRender(ImGui_ImplVulkanH_Window* wd, ImDrawData* drawData) { + VkResult err; + + VkSemaphore imageAcquiredSemaphore = wd->FrameSemaphores[wd->SemaphoreIndex].ImageAcquiredSemaphore; + VkSemaphore renderCompleteSemaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore; + err = vkAcquireNextImageKHR(mDevice, wd->Swapchain, UINT64_MAX, imageAcquiredSemaphore, VK_NULL_HANDLE, &wd->FrameIndex); + if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) { + mSwapChainRebuild = true; + return; + } + CheckVkResults(err); + + ImGui_ImplVulkanH_Frame* fd = &wd->Frames[wd->FrameIndex]; + { + err = vkWaitForFences(mDevice, 1, &fd->Fence, VK_TRUE, UINT64_MAX); // wait indefinitely instead of periodically checking + CheckVkResults(err); + + err = vkResetFences(mDevice, 1, &fd->Fence); + CheckVkResults(err); + } + { + err = vkResetCommandPool(mDevice, fd->CommandPool, 0); + CheckVkResults(err); + VkCommandBufferBeginInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + err = vkBeginCommandBuffer(fd->CommandBuffer, &info); + CheckVkResults(err); + } + { + VkRenderPassBeginInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + info.renderPass = wd->RenderPass; + info.framebuffer = fd->Framebuffer; + info.renderArea.extent.width = wd->Width; + info.renderArea.extent.height = wd->Height; + info.clearValueCount = 1; + info.pClearValues = &wd->ClearValue; + vkCmdBeginRenderPass(fd->CommandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE); + } + + // Record dear imgui primitives into command buffer + ImGui_ImplVulkan_RenderDrawData(drawData, fd->CommandBuffer); + + // Submit command buffer + vkCmdEndRenderPass(fd->CommandBuffer); + { + VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + VkSubmitInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + info.waitSemaphoreCount = 1; + info.pWaitSemaphores = &imageAcquiredSemaphore; + info.pWaitDstStageMask = &wait_stage; + info.commandBufferCount = 1; + info.pCommandBuffers = &fd->CommandBuffer; + info.signalSemaphoreCount = 1; + info.pSignalSemaphores = &renderCompleteSemaphore; + + err = vkEndCommandBuffer(fd->CommandBuffer); + CheckVkResults(err); + err = vkQueueSubmit(mQueue, 1, &info, fd->Fence); + CheckVkResults(err); + } + } + + void FramePresent(ImGui_ImplVulkanH_Window* wd) { + if (mSwapChainRebuild) { + return; + } + + VkSemaphore renderCompleteSemaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore; + VkPresentInfoKHR info = {}; + info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + info.waitSemaphoreCount = 1; + info.pWaitSemaphores = &renderCompleteSemaphore; + info.swapchainCount = 1; + info.pSwapchains = &wd->Swapchain; + info.pImageIndices = &wd->FrameIndex; + VkResult err = vkQueuePresentKHR(mQueue, &info); + if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) { + mSwapChainRebuild = true; + return; + } + CheckVkResults(err); + wd->SemaphoreIndex = (wd->SemaphoreIndex + 1) % wd->ImageCount; // Now we can use the next set of semaphores + } + + void CleanupVulkan() { + vkDestroyDescriptorPool(mDevice, mDescriptorPool, mAllocator); + + vkDestroyDevice(mDevice, mAllocator); + vkDestroyInstance(mInstance, mAllocator); + } + + void CleanupVulkanWindow() { + ImGui_ImplVulkanH_DestroyWindow(mInstance, mDevice, &mMainWindowData, mAllocator); + } + + static void CheckVkResults(VkResult err) { + if (err == 0) return; + + std::string message; + message += "Vulkan error: VkResult = "; + message += err; + + if (err < 0) { + throw std::runtime_error(message); + } else { + std::cerr << message << '\n'; + } + } + static void GlfwErrorCallback(int errorCode, const char* message) { + std::cerr << "GLFW Error " << errorCode << ": " << message << "\n"; + } +}; + +std::unique_ptr<RenderingBackend> RenderingBackend::CreateVulkanBackend() { + try { + return std::make_unique<VulkanBackend>(); + } catch (std::exception& e) { + return nullptr; + } +} + +#else // ^^ BUILD_CORE_WITH_VULKAN_BACKEND | ~BUILD_CORE_WITH_VULKAN_BACKEND vv + +std::unique_ptr<RenderingBackend> RenderingBackend::CreateVulkanBackend() { + return nullptr; +} + +#endif diff --git a/core/src/Entrypoint/Common.cpp b/core/src/Entrypoint/Common.cpp deleted file mode 100644 index c949830..0000000 --- a/core/src/Entrypoint/Common.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "Common.hpp" - -#include <backend/imgui_impl_glfw.h> -#include <iostream> - -#include <backend/imgui_impl_glfw.cpp> - -GLFWwindow* RenderingBackend::GetWindow() const { - return mWindow; -} - -void RenderingBackend::GlfwErrorCallback(int error, const char* message) { - std::cerr << "GLFW Error " << error << ": " << message << "\n"; -} diff --git a/core/src/Entrypoint/Common.hpp b/core/src/Entrypoint/Common.hpp deleted file mode 100644 index 216c885..0000000 --- a/core/src/Entrypoint/Common.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include <glad/glad.h> -#include <GLFW/glfw3.h> - -class RenderingBackend { -protected: - GLFWwindow* mWindow; - -public: - virtual ~RenderingBackend() = default; - virtual void BeginFrame() = 0; - virtual void EndFrame() = 0; - - GLFWwindow* GetWindow() const; - - /// Common GLFW error handle callback for each rendering backend to use. - static void GlfwErrorCallback(int error, const char* message); -}; diff --git a/core/src/Entrypoint/DirectX11.cpp b/core/src/Entrypoint/DirectX11.cpp deleted file mode 100644 index 617447c..0000000 --- a/core/src/Entrypoint/DirectX11.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "DirectX11.hpp" - -DirectX11Backend::DirectX11Backend() { - // TODO -} - -void DirectX11Backend::BeginFrame() { - // TODO -} - -void DirectX11Backend::EndFrame() { - // TODO -} diff --git a/core/src/Entrypoint/DirectX11.hpp b/core/src/Entrypoint/DirectX11.hpp deleted file mode 100644 index 134a20f..0000000 --- a/core/src/Entrypoint/DirectX11.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "Entrypoint/Common.hpp" - -class DirectX11Backend : public RenderingBackend { -public: - DirectX11Backend(); - virtual ~DirectX11Backend() = default; - virtual void BeginFrame() override; - virtual void EndFrame() override; -}; diff --git a/core/src/Entrypoint/DirectX12.cpp b/core/src/Entrypoint/DirectX12.cpp deleted file mode 100644 index 1769d24..0000000 --- a/core/src/Entrypoint/DirectX12.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "DirectX12.hpp" - -DirectX12Backend::DirectX12Backend() { - // TODO -} - -void DirectX12Backend::BeginFrame() { - // TODO -} - -void DirectX12Backend::EndFrame() { - // TODO -} diff --git a/core/src/Entrypoint/DirectX12.hpp b/core/src/Entrypoint/DirectX12.hpp deleted file mode 100644 index 8b9a4f0..0000000 --- a/core/src/Entrypoint/DirectX12.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "Entrypoint/Common.hpp" - -class DirectX12Backend : public RenderingBackend { -public: - DirectX12Backend(); - virtual ~DirectX12Backend() = default; - virtual void BeginFrame() override; - virtual void EndFrame() override; -}; diff --git a/core/src/Entrypoint/Metal.hpp b/core/src/Entrypoint/Metal.hpp deleted file mode 100644 index cd96d0f..0000000 --- a/core/src/Entrypoint/Metal.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "Entrypoint/Common.hpp" - -class MetalBackend : public RenderingBackend { -public: - MetalBackend(); - virtual ~MetalBackend() = default; - virtual void BeginFrame() override; - virtual void EndFrame() override; -}; diff --git a/core/src/Entrypoint/Metal.mm b/core/src/Entrypoint/Metal.mm deleted file mode 100644 index 3f69634..0000000 --- a/core/src/Entrypoint/Metal.mm +++ /dev/null @@ -1,13 +0,0 @@ -#include "Metal.hpp" - -MetalBackend::MetalBackend() { - // TODO -} - -void MetalBackend::BeginFrame() { - // TODO -} - -void MetalBackend::EndFrame() { - // TODO -} diff --git a/core/src/Entrypoint/OpenGL2.cpp b/core/src/Entrypoint/OpenGL2.cpp deleted file mode 100644 index 216399e..0000000 --- a/core/src/Entrypoint/OpenGL2.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "OpenGL2.hpp" - -#if BUILD_CORE_WITH_OPENGL2_BACKEND -# include <glad/glad.h> -# include <GLFW/glfw3.h> -# include <backend/imgui_impl_glfw.h> -# include <backend/imgui_impl_opengl2.h> -# include <imgui.h> -# include <stdexcept> - -# define IMGUI_IMPL_OPENGL_LOADER_GLAD -# include <backend/imgui_impl_opengl2.cpp> - -OpenGL2Backend::OpenGL2Backend() { - glfwSetErrorCallback(GlfwErrorCallback); - if (!glfwInit()) { - throw std::runtime_error("Failed to initialize GLFW."); - } - - mWindow = glfwCreateWindow(1280, 720, "Cplt", nullptr, nullptr); - if (mWindow == nullptr) { - throw std::runtime_error("Failed to create GLFW window."); - } - glfwMakeContextCurrent(mWindow); - glfwSwapInterval(1); // Enable vsync - - if (gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) == 0) { - throw std::runtime_error("Failed to initialize OpenGL."); - } - - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - - ImGui_ImplGlfw_InitForOpenGL(mWindow, true); - ImGui_ImplOpenGL2_Init(); -} - -OpenGL2Backend::~OpenGL2Backend() { - ImGui_ImplOpenGL2_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); - - glfwDestroyWindow(mWindow); - glfwTerminate(); -} - -void OpenGL2Backend::BeginFrame() { - glfwPollEvents(); - - ImGui_ImplOpenGL2_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); -} - -void OpenGL2Backend::EndFrame() { - int displayWidth, displayHeight; - glfwGetFramebufferSize(mWindow, &displayWidth, &displayHeight); - glViewport(0, 0, displayWidth, displayHeight); - - const ImVec4 kClearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - glClearColor(kClearColor.x * kClearColor.w, kClearColor.y * kClearColor.w, kClearColor.z * kClearColor.w, kClearColor.w); - glClear(GL_COLOR_BUFFER_BIT); - - ImGui::Render(); - ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); - - glfwMakeContextCurrent(mWindow); - glfwSwapBuffers(mWindow); -} - -#else // ^^ BUILD_CORE_WITH_OPENGL2_BACKEND | !BUILD_CORE_WITH_OPENGL2_BACKEND vv -# include <stdexcept> - -OpenGL2Backend::OpenGL2Backend() { - throw std::runtime_error("Backend opengl2 is not available in this build.\n"); -} - -OpenGL2Backend::~OpenGL2Backend() { -} - -void OpenGL2Backend::BeginFrame() { -} - -void OpenGL2Backend::EndFrame() { -} - -#endif diff --git a/core/src/Entrypoint/OpenGL2.hpp b/core/src/Entrypoint/OpenGL2.hpp deleted file mode 100644 index 86fbad7..0000000 --- a/core/src/Entrypoint/OpenGL2.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "Entrypoint/Common.hpp" - -#include <glad/glad.h> - -#include <GLFW/glfw3.h> - -class OpenGL2Backend : public RenderingBackend { -public: - OpenGL2Backend(); - virtual ~OpenGL2Backend(); - virtual void BeginFrame() override; - virtual void EndFrame() override; -}; diff --git a/core/src/Entrypoint/OpenGL3.cpp b/core/src/Entrypoint/OpenGL3.cpp deleted file mode 100644 index 7d5cae1..0000000 --- a/core/src/Entrypoint/OpenGL3.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "OpenGL3.hpp" - -#if BUILD_CORE_WITH_OPENGL3_BACKEND -# include <glad/glad.h> -# include <GLFW/glfw3.h> -# include <backend/imgui_impl_glfw.h> -# include <backend/imgui_impl_opengl3.h> -# include <imgui.h> -# include <stdexcept> - -# define IMGUI_IMPL_OPENGL_LOADER_GLAD -# include <backend/imgui_impl_opengl3.cpp> - -OpenGL3Backend::OpenGL3Backend() { - glfwSetErrorCallback(GlfwErrorCallback); - if (!glfwInit()) { - throw std::runtime_error("Failed to initialize GLFW."); - } - -# if PLATFORM_APPLE - // GL 3.2 + GLSL 150 - const char* glslVersion = "#version 150"; - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac -# else - // GL 3.0 + GLSL 130 - const char* glslVersion = "#version 130"; - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only - //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only -# endif - - mWindow = glfwCreateWindow(1280, 720, "Cplt", nullptr, nullptr); - if (mWindow == nullptr) { - throw std::runtime_error("Failed to create GLFW window."); - } - glfwMakeContextCurrent(mWindow); - glfwSwapInterval(1); // Enable vsync - - if (gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) == 0) { - throw std::runtime_error("Failed to initialize OpenGL."); - } - - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - - ImGui_ImplGlfw_InitForOpenGL(mWindow, true); - ImGui_ImplOpenGL3_Init(glslVersion); -} - -OpenGL3Backend::~OpenGL3Backend() { - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); - - glfwDestroyWindow(mWindow); - glfwTerminate(); -} - -void OpenGL3Backend::BeginFrame() { - glfwPollEvents(); - - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); -} - -void OpenGL3Backend::EndFrame() { - int displayWidth, displayHeight; - glfwGetFramebufferSize(mWindow, &displayWidth, &displayHeight); - glViewport(0, 0, displayWidth, displayHeight); - - const ImVec4 kClearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - glClearColor(kClearColor.x * kClearColor.w, kClearColor.y * kClearColor.w, kClearColor.z * kClearColor.w, kClearColor.w); - glClear(GL_COLOR_BUFFER_BIT); - - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - glfwSwapBuffers(mWindow); -} - -#else // ^^ BUILD_CORE_WITH_OPENGL3_BACKEND | !BUILD_CORE_WITH_OPENGL3_BACKEND vv -# include <stdexcept> - -OpenGL3Backend::OpenGL3Backend() { - throw std::runtime_error("Backend opengl3 is not available in this build.\n"); -} - -OpenGL3Backend::~OpenGL3Backend() { -} - -void OpenGL3Backend::BeginFrame() { -} - -void OpenGL3Backend::EndFrame() { -} - -#endif diff --git a/core/src/Entrypoint/OpenGL3.hpp b/core/src/Entrypoint/OpenGL3.hpp deleted file mode 100644 index 52978fa..0000000 --- a/core/src/Entrypoint/OpenGL3.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "Entrypoint/Common.hpp" - -#include <glad/glad.h> -#include <GLFW/glfw3.h> - -class OpenGL3Backend : public RenderingBackend { -public: - OpenGL3Backend(); - virtual ~OpenGL3Backend() ; - virtual void BeginFrame() override; - virtual void EndFrame() override; -}; diff --git a/core/src/Entrypoint/Vulkan.cpp b/core/src/Entrypoint/Vulkan.cpp deleted file mode 100644 index 5af1772..0000000 --- a/core/src/Entrypoint/Vulkan.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "Vulkan.hpp" - -VulkanBackend::VulkanBackend() { - // TODO -} - -void VulkanBackend::BeginFrame() { - // TODO -} - -void VulkanBackend::EndFrame() { - // TODO -} diff --git a/core/src/Entrypoint/Vulkan.hpp b/core/src/Entrypoint/Vulkan.hpp deleted file mode 100644 index 6404806..0000000 --- a/core/src/Entrypoint/Vulkan.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "Entrypoint/Common.hpp" - -class VulkanBackend : public RenderingBackend { -public: - VulkanBackend(); - virtual ~VulkanBackend() = default; - virtual void BeginFrame() override; - virtual void EndFrame() override; -}; diff --git a/core/src/Entrypoint/main.cpp b/core/src/Entrypoint/main.cpp index 4f5dc4b..3016467 100644 --- a/core/src/Entrypoint/main.cpp +++ b/core/src/Entrypoint/main.cpp @@ -1,15 +1,10 @@ -#include "Entrypoint/Common.hpp" -#include "Entrypoint/DirectX11.hpp" -#include "Entrypoint/DirectX12.hpp" -#include "Entrypoint/Metal.hpp" -#include "Entrypoint/OpenGL2.hpp" -#include "Entrypoint/OpenGL3.hpp" -#include "Entrypoint/Vulkan.hpp" +#include "Entrypoint/Backend.hpp" #include "Model/GlobalStates.hpp" #include "UI/Localization.hpp" #include "UI/States.hpp" #include "UI/UI.hpp" #include "Utils/I18n.hpp" +#include "Utils/ScopeGuard.hpp" #include "Utils/Sigslot.hpp" #include <IconsFontAwesome.h> @@ -29,57 +24,47 @@ using namespace std::literals::string_view_literals; static std::unique_ptr<RenderingBackend> CreateDefaultBackend() { #if PLATFORM_WIN32 # if BUILD_CORE_WITH_DX12_BACKEND - try { - auto backend = std::make_unique<DirectX12Backend>(); + if (auto backend = RenderingBackend::CreateDx12Backend()) { return backend; - } catch (const std::exception&) { } -# elif BUILD_CORE_WITH_DX11_BACKEND - try { - auto backend = std::make_unique<DirectX11Backend>(); +# endif +# if BUILD_CORE_WITH_DX11_BACKEND + if (auto backend = RenderingBackend::CreateDx11Backend()) { return backend; - } catch (const std::exception&) { } -# elif BUILD_CORE_WITH_VULKAN_BACKEND - try { - auto backend = std::make_unique<VulkanBackend>(); +# endif +# if BUILD_CORE_WITH_VULKAN_BACKEND + if (auto backend = RenderingBackend::CreateVulkanBackend()) { return backend; - } catch (const std::exception&) { } -# elif BUILD_CORE_WITH_OPENGL3_BACKEND - try { - auto backend = std::make_unique<OpenGL3Backend>(); +# endif +# if BUILD_CORE_WITH_OPENGL3_BACKEND + if (auto backend = RenderingBackend::CreateOpenGL3Backend()) { return backend; - } catch (const std::exception&) { } -# elif BUILD_CORE_WITH_OPENGL2_BACKEND - try { - auto backend = std::make_unique<OpenGL2Backend>(); +# endif +# if BUILD_CORE_WITH_OPENGL2_BACKEND + if (auto backend = RenderingBackend::CreateOpenGL2Backend()) { return backend; - } catch (const std::exception&) { } # endif #elif PLATFORM_MACOS // We currently only support using metal on macos - backend = std::make_unique<MetalBackend>(); + return RenderingBackend::CreateMetalBackend(); #elif PLATFORM_LINUX # if BUILD_CORE_WITH_VULKAN_BACKEND - try { - auto backend = std::make_unique<VulkanBackend>(); + if (auto backend = RenderingBackend::CreateVulkanBackend()) { return backend; - } catch (const std::exception&) { } -# elif BUILD_CORE_WITH_OPENGL3_BACKEND - try { - auto backend = std::make_unique<OpenGL3Backend>(); +# endif +# if BUILD_CORE_WITH_OPENGL3_BACKEND + if (auto backend = RenderingBackend::CreateOpenGL3Backend()) { return backend; - } catch (const std::exception&) { } -# elif BUILD_CORE_WITH_OPENGL2_BACKEND - try { - auto backend = std::make_unique<OpenGL2Backend>(); +# endif +# if BUILD_CORE_WITH_OPENGL2_BACKEND + if (auto backend = RenderingBackend::CreateOpenGL2Backend()) { return backend; - } catch (const std::exception&) { } # endif #endif @@ -91,24 +76,23 @@ static std::unique_ptr<RenderingBackend> CreateBackend(std::string_view option) if (option == "default") { return CreateDefaultBackend(); } else if (option == "opengl2") { - return std::make_unique<OpenGL2Backend>(); + return RenderingBackend::CreateOpenGL2Backend(); } else if (option == "opengl3") { - return std::make_unique<OpenGL3Backend>(); + return RenderingBackend::CreateOpenGL3Backend(); } else if (option == "vulkan") { - return std::make_unique<VulkanBackend>(); + return RenderingBackend::CreateVulkanBackend(); } else if (option == "dx11") { - return std::make_unique<DirectX11Backend>(); + return RenderingBackend::CreateDx11Backend(); } else if (option == "dx12") { - return std::make_unique<DirectX12Backend>(); + return RenderingBackend::CreateDx12Backend(); } else if (option == "metal") { - return std::make_unique<MetalBackend>(); + return RenderingBackend::CreateMetalBackend(); } else { std::string message; message += "Unknown backend '"; message += option; message += "'.\n"; throw std::runtime_error(message); - return nullptr; } } @@ -175,18 +159,13 @@ int main(int argc, char* argv[]) { GlobalStates::Init(); } } + DEFER { GlobalStates::Shutdown(); }; UIState::Init(); + DEFER { UIState::Shutdown(); }; - auto window = backend->GetWindow(); - while (!glfwWindowShouldClose(window)) { - backend->BeginFrame(); - UI::MainWindow(); - backend->EndFrame(); - } - - UIState::Shutdown(); - GlobalStates::Shutdown(); + // Main loop + backend->RunUntilWindowClose(&UI::MainWindow); return 0; } |