#include #include #include #include #include #include "XCEngine/Core/Containers/String.h" #include "XCEngine/Debug/ConsoleLogSink.h" #include "XCEngine/Debug/Logger.h" #include "XCEngine/RHI/RHIEnums.h" #include "XCEngine/RHI/RHIResourceView.h" #include "XCEngine/RHI/Vulkan/VulkanCommandList.h" #include "XCEngine/RHI/Vulkan/VulkanCommandQueue.h" #include "XCEngine/RHI/Vulkan/VulkanDevice.h" #include "XCEngine/RHI/Vulkan/VulkanScreenshot.h" #include "XCEngine/RHI/Vulkan/VulkanSwapChain.h" #include "XCEngine/RHI/Vulkan/VulkanTexture.h" using namespace XCEngine::Containers; using namespace XCEngine::Debug; using namespace XCEngine::RHI; namespace { constexpr int kWidth = 1280; constexpr int kHeight = 720; constexpr int kTargetFrameCount = 30; VulkanDevice g_device; VulkanCommandQueue g_commandQueue; VulkanSwapChain g_swapChain; VulkanCommandList g_commandList; VulkanScreenshot g_screenshot; std::vector g_backBufferViews; HWND g_window = nullptr; void Log(const char* format, ...) { char buffer[1024] = {}; va_list args; va_start(args, format); vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); Logger::Get().Debug(LogCategory::Rendering, String(buffer)); } LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CLOSE: PostQuitMessage(0); return 0; default: return DefWindowProc(hwnd, msg, wParam, lParam); } } void ShutdownViews() { for (RHIResourceView* view : g_backBufferViews) { if (view != nullptr) { view->Shutdown(); delete view; } } g_backBufferViews.clear(); } void ShutdownVulkan() { ShutdownViews(); g_commandList.Shutdown(); g_swapChain.Shutdown(); g_commandQueue.Shutdown(); g_device.Shutdown(); } bool InitVulkan() { RHIDeviceDesc deviceDesc = {}; deviceDesc.adapterIndex = 0; deviceDesc.enableDebugLayer = false; deviceDesc.enableGPUValidation = false; if (!g_device.Initialize(deviceDesc)) { Log("[ERROR] Failed to initialize Vulkan device"); return false; } if (!g_commandQueue.Initialize(&g_device, CommandQueueType::Direct)) { Log("[ERROR] Failed to initialize Vulkan command queue"); return false; } if (!g_swapChain.Initialize(&g_device, &g_commandQueue, g_window, kWidth, kHeight)) { Log("[ERROR] Failed to initialize Vulkan swap chain"); return false; } if (!g_commandList.Initialize(&g_device)) { Log("[ERROR] Failed to initialize Vulkan command list"); return false; } Log("[INFO] Vulkan initialized successfully"); return true; } RHIResourceView* GetCurrentBackBufferView() { const uint32_t backBufferIndex = g_swapChain.GetCurrentBackBufferIndex(); if (g_backBufferViews.size() <= backBufferIndex) { g_backBufferViews.resize(backBufferIndex + 1, nullptr); } if (g_backBufferViews[backBufferIndex] == nullptr) { ResourceViewDesc viewDesc = {}; viewDesc.dimension = ResourceViewDimension::Texture2D; viewDesc.format = static_cast(Format::R8G8B8A8_UNorm); viewDesc.arraySize = 1; g_backBufferViews[backBufferIndex] = g_device.CreateRenderTargetView(g_swapChain.GetCurrentBackBuffer(), viewDesc); if (g_backBufferViews[backBufferIndex] == nullptr) { Log("[ERROR] Failed to create render target view for swap chain image %u", backBufferIndex); } } return g_backBufferViews[backBufferIndex]; } bool RenderFrame() { if (!g_swapChain.AcquireNextImage()) { Log("[ERROR] Failed to acquire next swap chain image"); return false; } RHIResourceView* renderTargetView = GetCurrentBackBufferView(); if (renderTargetView == nullptr) { return false; } g_commandList.Reset(); g_commandList.SetRenderTargets(1, &renderTargetView, nullptr); Viewport viewport = { 0.0f, 0.0f, static_cast(kWidth), static_cast(kHeight), 0.0f, 1.0f }; Rect scissorRect = { 0, 0, kWidth, kHeight }; g_commandList.SetViewport(viewport); g_commandList.SetScissorRect(scissorRect); g_commandList.Clear(1.0f, 0.0f, 0.0f, 1.0f, 1); g_commandList.Close(); void* commandLists[] = { &g_commandList }; g_commandQueue.ExecuteCommandLists(1, commandLists); return true; } } // namespace int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nShowCmd) { Logger::Get().Initialize(); Logger::Get().AddSink(std::make_unique()); Logger::Get().SetMinimumLevel(LogLevel::Debug); WNDCLASSEXW wc = {}; wc.cbSize = sizeof(WNDCLASSEXW); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.lpszClassName = L"XCEngine_Vulkan_Minimal_Test"; if (!RegisterClassExW(&wc)) { Log("[ERROR] Failed to register window class"); Logger::Get().Shutdown(); return -1; } RECT rect = { 0, 0, kWidth, kHeight }; AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); g_window = CreateWindowExW( 0, L"XCEngine_Vulkan_Minimal_Test", L"Vulkan Minimal Integration Test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, nullptr, nullptr, hInstance, nullptr); if (g_window == nullptr) { Log("[ERROR] Failed to create window"); Logger::Get().Shutdown(); return -1; } if (!InitVulkan()) { ShutdownVulkan(); DestroyWindow(g_window); g_window = nullptr; Logger::Get().Shutdown(); return -1; } ShowWindow(g_window, nShowCmd); UpdateWindow(g_window); MSG msg = {}; int frameCount = 0; int exitCode = 0; while (true) { if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { break; } TranslateMessage(&msg); DispatchMessageW(&msg); continue; } if (!RenderFrame()) { exitCode = -1; break; } ++frameCount; Log("[INFO] Rendered frame %d", frameCount); if (frameCount >= kTargetFrameCount) { g_commandQueue.WaitForIdle(); if (!g_screenshot.Capture(&g_device, &g_swapChain, "minimal.ppm")) { Log("[ERROR] Failed to capture screenshot"); exitCode = -1; } break; } g_swapChain.Present(0, 0); } ShutdownVulkan(); if (g_window != nullptr) { DestroyWindow(g_window); g_window = nullptr; } Logger::Get().Shutdown(); return exitCode; }