diff options
Diffstat (limited to 'app/source/Cplt/Entrypoint')
-rw-r--r-- | app/source/Cplt/Entrypoint/Backend.hpp | 23 | ||||
-rw-r--r-- | app/source/Cplt/Entrypoint/Backend_DirectX11.cpp | 250 | ||||
-rw-r--r-- | app/source/Cplt/Entrypoint/Backend_DirectX12.cpp | 470 | ||||
-rw-r--r-- | app/source/Cplt/Entrypoint/Backend_Metal.mm | 40 | ||||
-rw-r--r-- | app/source/Cplt/Entrypoint/Backend_OpenGL2.cpp | 106 | ||||
-rw-r--r-- | app/source/Cplt/Entrypoint/Backend_OpenGL3.cpp | 121 | ||||
-rw-r--r-- | app/source/Cplt/Entrypoint/Backend_Vulkan.cpp | 438 | ||||
-rw-r--r-- | app/source/Cplt/Entrypoint/main.cpp | 163 |
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 |