aboutsummaryrefslogtreecommitdiff
path: root/app/source/Cplt/Entrypoint
diff options
context:
space:
mode:
Diffstat (limited to 'app/source/Cplt/Entrypoint')
-rw-r--r--app/source/Cplt/Entrypoint/Backend.hpp23
-rw-r--r--app/source/Cplt/Entrypoint/Backend_DirectX11.cpp250
-rw-r--r--app/source/Cplt/Entrypoint/Backend_DirectX12.cpp470
-rw-r--r--app/source/Cplt/Entrypoint/Backend_Metal.mm40
-rw-r--r--app/source/Cplt/Entrypoint/Backend_OpenGL2.cpp106
-rw-r--r--app/source/Cplt/Entrypoint/Backend_OpenGL3.cpp121
-rw-r--r--app/source/Cplt/Entrypoint/Backend_Vulkan.cpp438
-rw-r--r--app/source/Cplt/Entrypoint/main.cpp163
8 files changed, 1611 insertions, 0 deletions
diff --git a/app/source/Cplt/Entrypoint/Backend.hpp b/app/source/Cplt/Entrypoint/Backend.hpp
new file mode 100644
index 0000000..ca391e6
--- /dev/null
+++ b/app/source/Cplt/Entrypoint/Backend.hpp
@@ -0,0 +1,23 @@
+#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/app/source/Cplt/Entrypoint/Backend_DirectX11.cpp b/app/source/Cplt/Entrypoint/Backend_DirectX11.cpp
new file mode 100644
index 0000000..4dc33f7
--- /dev/null
+++ b/app/source/Cplt/Entrypoint/Backend_DirectX11.cpp
@@ -0,0 +1,250 @@
+#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/app/source/Cplt/Entrypoint/Backend_DirectX12.cpp b/app/source/Cplt/Entrypoint/Backend_DirectX12.cpp
new file mode 100644
index 0000000..fd4a531
--- /dev/null
+++ b/app/source/Cplt/Entrypoint/Backend_DirectX12.cpp
@@ -0,0 +1,470 @@
+#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/app/source/Cplt/Entrypoint/Backend_Metal.mm b/app/source/Cplt/Entrypoint/Backend_Metal.mm
new file mode 100644
index 0000000..276bef2
--- /dev/null
+++ b/app/source/Cplt/Entrypoint/Backend_Metal.mm
@@ -0,0 +1,40 @@
+#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/app/source/Cplt/Entrypoint/Backend_OpenGL2.cpp b/app/source/Cplt/Entrypoint/Backend_OpenGL2.cpp
new file mode 100644
index 0000000..0f20997
--- /dev/null
+++ b/app/source/Cplt/Entrypoint/Backend_OpenGL2.cpp
@@ -0,0 +1,106 @@
+#include <Cplt/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_CUSTOM
+# 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/app/source/Cplt/Entrypoint/Backend_OpenGL3.cpp b/app/source/Cplt/Entrypoint/Backend_OpenGL3.cpp
new file mode 100644
index 0000000..28a34ca
--- /dev/null
+++ b/app/source/Cplt/Entrypoint/Backend_OpenGL3.cpp
@@ -0,0 +1,121 @@
+#include <Cplt/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_CUSTOM
+# 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 defined(__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/app/source/Cplt/Entrypoint/Backend_Vulkan.cpp b/app/source/Cplt/Entrypoint/Backend_Vulkan.cpp
new file mode 100644
index 0000000..280a82b
--- /dev/null
+++ b/app/source/Cplt/Entrypoint/Backend_Vulkan.cpp
@@ -0,0 +1,438 @@
+#include <Cplt/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/app/source/Cplt/Entrypoint/main.cpp b/app/source/Cplt/Entrypoint/main.cpp
new file mode 100644
index 0000000..8f67d32
--- /dev/null
+++ b/app/source/Cplt/Entrypoint/main.cpp
@@ -0,0 +1,163 @@
+#include <Cplt/Entrypoint/Backend.hpp>
+#include <Cplt/Model/GlobalStates.hpp>
+#include <Cplt/UI/UI.hpp>
+#include <Cplt/Utils/I18n.hpp>
+#include <Cplt/Utils/ScopeGuard.hpp>
+#include <Cplt/Utils/Sigslot.hpp>
+
+#include <IconsFontAwesome.h>
+#include <imgui.h>
+#include <argparse/argparse.hpp>
+#include <filesystem>
+#include <iostream>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+
+namespace fs = std::filesystem;
+using namespace std::literals::string_literals;
+using namespace std::literals::string_view_literals;
+
+static std::unique_ptr<RenderingBackend> CreateDefaultBackend()
+{
+#if defined(_WIN32)
+# if BUILD_CORE_WITH_DX12_BACKEND
+ if (auto backend = RenderingBackend::CreateDx12Backend()) {
+ return backend;
+ }
+# endif
+# if BUILD_CORE_WITH_DX11_BACKEND
+ if (auto backend = RenderingBackend::CreateDx11Backend()) {
+ return backend;
+ }
+# endif
+# if BUILD_CORE_WITH_VULKAN_BACKEND
+ if (auto backend = RenderingBackend::CreateVulkanBackend()) {
+ return backend;
+ }
+# endif
+# if BUILD_CORE_WITH_OPENGL3_BACKEND
+ if (auto backend = RenderingBackend::CreateOpenGL3Backend()) {
+ return backend;
+ }
+# endif
+# if BUILD_CORE_WITH_OPENGL2_BACKEND
+ if (auto backend = RenderingBackend::CreateOpenGL2Backend()) {
+ return backend;
+ }
+# endif
+#elif defined(__APPLE__)
+ // We currently only support using metal on macos
+ return RenderingBackend::CreateMetalBackend();
+#elif defined(__linux__)
+# if BUILD_CORE_WITH_VULKAN_BACKEND
+ if (auto backend = RenderingBackend::CreateVulkanBackend()) {
+ return backend;
+ }
+# endif
+# if BUILD_CORE_WITH_OPENGL3_BACKEND
+ if (auto backend = RenderingBackend::CreateOpenGL3Backend()) {
+ return backend;
+ }
+# endif
+# if BUILD_CORE_WITH_OPENGL2_BACKEND
+ if (auto backend = RenderingBackend::CreateOpenGL2Backend()) {
+ return backend;
+ }
+# endif
+#endif
+
+ return nullptr;
+}
+
+static std::unique_ptr<RenderingBackend> CreateBackend(std::string_view option)
+{
+ if (option == "default") {
+ return CreateDefaultBackend();
+ } else if (option == "opengl2") {
+ return RenderingBackend::CreateOpenGL2Backend();
+ } else if (option == "opengl3") {
+ return RenderingBackend::CreateOpenGL3Backend();
+ } else if (option == "vulkan") {
+ return RenderingBackend::CreateVulkanBackend();
+ } else if (option == "dx11") {
+ return RenderingBackend::CreateDx11Backend();
+ } else if (option == "dx12") {
+ return RenderingBackend::CreateDx12Backend();
+ } else if (option == "metal") {
+ return RenderingBackend::CreateMetalBackend();
+ } else {
+ std::string message;
+ message += "Unknown backend '";
+ message += option;
+ message += "'.\n";
+ throw std::runtime_error(message);
+ }
+}
+
+#ifdef DOCTEST_CONFIG_DISABLE
+int main(int argc, char* argv[])
+{
+ argparse::ArgumentParser parser;
+ parser.add_argument("--global-data-directory")
+ .help("Directory in which global data (such as recently used projects) are saved to. Use 'default' to use the default directory on each platform.")
+ .default_value("default"s);
+ parser.add_argument("--rendering-backend")
+ .help("Which rendering backend to use. If equals 'default', the preferred API for each platform will be used")
+ .default_value("default"s);
+
+ try {
+ parser.parse_args(argc, argv);
+ } catch (const std::runtime_error& error) {
+ std::cout << error.what() << '\n';
+ std::cout << parser;
+ return -1;
+ }
+
+ auto backendOption = parser.get<std::string>("--rendering-backend");
+ auto backend = CreateBackend(backendOption);
+
+ auto& io = ImGui::GetIO();
+
+ // Disable saving window positions
+ io.IniFilename = nullptr;
+ // Disable log (dump widget tree) file, we don't trigger it but just to be safe
+ io.LogFilename = nullptr;
+
+ // Light mode because all major OS's default theme is white
+ // TODO follow system theme
+ ImGui::StyleColorsLight();
+
+ // Configure default fonts
+ {
+ // Includes latin alphabet, although for some reason smaller than if rendered using 18 point NotoSans regular
+ io.Fonts->AddFontFromFileTTF("fonts/NotoSansSC-Regular.otf", 18, nullptr, io.Fonts->GetGlyphRangesChineseFull());
+
+ ImWchar iconRanges[] = { ICON_MIN_FA, ICON_MAX_FA };
+ ImFontConfig config;
+ config.MergeMode = true;
+ io.Fonts->AddFontFromFileTTF("fonts/FontAwesome5-Solid.otf", 14, &config, iconRanges);
+ }
+
+ auto dataDirOption = parser.get<std::string>("--global-data-directory");
+ if (dataDirOption == "default") {
+ GlobalStates::Init();
+ } else {
+ fs::path path(dataDirOption);
+ GlobalStates::Init(std::move(path));
+ }
+ DEFER
+ {
+ GlobalStates::Shutdown();
+ };
+
+ // Main loop
+ backend->RunUntilWindowClose(&UI::MainWindow);
+
+ return 0;
+}
+#else
+# define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+# include <doctest/doctest.h>
+#endif