#include #include #include #include #include #include #include #include "XCEngine/Core/Containers/String.h" #include "XCEngine/Debug/ConsoleLogSink.h" #include "XCEngine/Debug/Logger.h" #include "XCEngine/RHI/RHIBuffer.h" #include "XCEngine/RHI/RHIDescriptorPool.h" #include "XCEngine/RHI/RHIDescriptorSet.h" #include "XCEngine/RHI/RHIEnums.h" #include "XCEngine/RHI/RHIPipelineLayout.h" #include "XCEngine/RHI/RHIPipelineState.h" #include "XCEngine/RHI/RHIResourceView.h" #include "XCEngine/RHI/RHISampler.h" #include "XCEngine/RHI/RHITexture.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" #include "third_party/stb/stb_image.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; struct Vertex { float pos[4]; float uv[2]; }; constexpr Vertex kQuadVertices[] = { {{-0.5f, -0.5f, 0.0f, 1.0f}, {0.0f, 1.0f}}, {{-0.5f, 0.5f, 0.0f, 1.0f}, {0.0f, 0.0f}}, {{ 0.5f, -0.5f, 0.0f, 1.0f}, {1.0f, 1.0f}}, {{ 0.5f, 0.5f, 0.0f, 1.0f}, {1.0f, 0.0f}}, }; constexpr uint32_t kQuadIndices[] = {0, 1, 2, 2, 1, 3}; const char kQuadVertexShader[] = R"(#version 450 layout(location = 0) in vec4 aPosition; layout(location = 1) in vec2 aTexCoord; layout(location = 0) out vec2 vTexCoord; void main() { gl_Position = aPosition; vTexCoord = aTexCoord; } )"; const char kQuadFragmentShader[] = R"(#version 450 layout(set = 0, binding = 0) uniform texture2D uTexture; layout(set = 1, binding = 0) uniform sampler uSampler; layout(location = 0) in vec2 vTexCoord; layout(location = 0) out vec4 fragColor; void main() { fragColor = texture(sampler2D(uTexture, uSampler), vTexCoord); } )"; VulkanDevice g_device; VulkanCommandQueue g_commandQueue; VulkanSwapChain g_swapChain; VulkanCommandList g_commandList; VulkanScreenshot g_screenshot; std::vector g_backBufferViews; RHIBuffer* g_vertexBuffer = nullptr; RHIResourceView* g_vertexBufferView = nullptr; RHIBuffer* g_indexBuffer = nullptr; RHIResourceView* g_indexBufferView = nullptr; RHITexture* g_texture = nullptr; RHIResourceView* g_textureView = nullptr; RHISampler* g_sampler = nullptr; RHIDescriptorPool* g_texturePool = nullptr; RHIDescriptorSet* g_textureSet = nullptr; RHIDescriptorPool* g_samplerPool = nullptr; RHIDescriptorSet* g_samplerSet = nullptr; RHIPipelineLayout* g_pipelineLayout = nullptr; RHIPipelineState* g_pipelineState = nullptr; HWND g_window = nullptr; template void ShutdownAndDelete(T*& object) { if (object != nullptr) { object->Shutdown(); delete object; object = 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)); } std::filesystem::path GetExecutableDirectory() { char exePath[MAX_PATH] = {}; const DWORD length = GetModuleFileNameA(nullptr, exePath, MAX_PATH); if (length == 0 || length >= MAX_PATH) { return std::filesystem::current_path(); } return std::filesystem::path(exePath).parent_path(); } std::filesystem::path ResolveRuntimePath(const char* relativePath) { return GetExecutableDirectory() / relativePath; } 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); } } GraphicsPipelineDesc CreateQuadPipelineDesc() { GraphicsPipelineDesc desc = {}; desc.pipelineLayout = g_pipelineLayout; desc.topologyType = static_cast(PrimitiveTopologyType::Triangle); desc.renderTargetFormats[0] = static_cast(Format::R8G8B8A8_UNorm); desc.depthStencilFormat = static_cast(Format::Unknown); desc.sampleCount = 1; desc.rasterizerState.fillMode = static_cast(FillMode::Solid); desc.rasterizerState.cullMode = static_cast(CullMode::None); desc.rasterizerState.frontFace = static_cast(FrontFace::CounterClockwise); desc.rasterizerState.depthClipEnable = true; desc.depthStencilState.depthTestEnable = false; desc.depthStencilState.depthWriteEnable = false; desc.depthStencilState.stencilEnable = false; InputElementDesc position = {}; position.semanticName = "POSITION"; position.semanticIndex = 0; position.format = static_cast(Format::R32G32B32A32_Float); position.inputSlot = 0; position.alignedByteOffset = 0; desc.inputLayout.elements.push_back(position); InputElementDesc texcoord = {}; texcoord.semanticName = "TEXCOORD"; texcoord.semanticIndex = 0; texcoord.format = static_cast(Format::R32G32_Float); texcoord.inputSlot = 0; texcoord.alignedByteOffset = sizeof(float) * 4; desc.inputLayout.elements.push_back(texcoord); desc.vertexShader.source.assign(kQuadVertexShader, kQuadVertexShader + std::strlen(kQuadVertexShader)); desc.vertexShader.sourceLanguage = ShaderLanguage::GLSL; desc.vertexShader.profile = L"vs"; desc.fragmentShader.source.assign(kQuadFragmentShader, kQuadFragmentShader + std::strlen(kQuadFragmentShader)); desc.fragmentShader.sourceLanguage = ShaderLanguage::GLSL; desc.fragmentShader.profile = L"fs"; return desc; } void ShutdownViews() { for (RHIResourceView* view : g_backBufferViews) { if (view != nullptr) { view->Shutdown(); delete view; } } g_backBufferViews.clear(); } void ShutdownQuadResources() { ShutdownAndDelete(g_pipelineState); ShutdownAndDelete(g_pipelineLayout); ShutdownAndDelete(g_textureSet); ShutdownAndDelete(g_samplerSet); ShutdownAndDelete(g_texturePool); ShutdownAndDelete(g_samplerPool); ShutdownAndDelete(g_sampler); ShutdownAndDelete(g_textureView); ShutdownAndDelete(g_texture); ShutdownAndDelete(g_vertexBufferView); ShutdownAndDelete(g_indexBufferView); ShutdownAndDelete(g_vertexBuffer); ShutdownAndDelete(g_indexBuffer); } void ShutdownVulkan() { ShutdownQuadResources(); 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; } bool LoadQuadTexture() { const std::filesystem::path texturePath = ResolveRuntimePath("Res/Image/earth.png"); const std::string texturePathString = texturePath.string(); stbi_set_flip_vertically_on_load(0); int width = 0; int height = 0; int channels = 0; stbi_uc* pixels = stbi_load(texturePathString.c_str(), &width, &height, &channels, STBI_rgb_alpha); if (pixels == nullptr) { Log("[ERROR] Failed to load texture: %s", texturePathString.c_str()); return false; } TextureDesc textureDesc = {}; textureDesc.width = static_cast(width); textureDesc.height = static_cast(height); textureDesc.depth = 1; textureDesc.mipLevels = 1; textureDesc.arraySize = 1; textureDesc.format = static_cast(Format::R8G8B8A8_UNorm); textureDesc.textureType = static_cast(TextureType::Texture2D); textureDesc.sampleCount = 1; textureDesc.sampleQuality = 0; textureDesc.flags = 0; g_texture = g_device.CreateTexture( textureDesc, pixels, static_cast(width) * static_cast(height) * 4, static_cast(width) * 4); stbi_image_free(pixels); if (g_texture == nullptr) { Log("[ERROR] Failed to create texture"); return false; } ResourceViewDesc textureViewDesc = {}; textureViewDesc.format = static_cast(Format::R8G8B8A8_UNorm); textureViewDesc.dimension = ResourceViewDimension::Texture2D; textureViewDesc.mipLevel = 0; textureViewDesc.arraySize = 1; g_textureView = g_device.CreateShaderResourceView(g_texture, textureViewDesc); if (g_textureView == nullptr) { Log("[ERROR] Failed to create texture view"); return false; } SamplerDesc samplerDesc = {}; samplerDesc.filter = static_cast(FilterMode::Linear); samplerDesc.addressU = static_cast(TextureAddressMode::Clamp); samplerDesc.addressV = static_cast(TextureAddressMode::Clamp); samplerDesc.addressW = static_cast(TextureAddressMode::Clamp); samplerDesc.mipLodBias = 0.0f; samplerDesc.maxAnisotropy = 1; samplerDesc.comparisonFunc = static_cast(ComparisonFunc::Always); samplerDesc.borderColorR = 0.0f; samplerDesc.borderColorG = 0.0f; samplerDesc.borderColorB = 0.0f; samplerDesc.borderColorA = 0.0f; samplerDesc.minLod = 0.0f; samplerDesc.maxLod = 1000.0f; g_sampler = g_device.CreateSampler(samplerDesc); if (g_sampler == nullptr) { Log("[ERROR] Failed to create sampler"); return false; } DescriptorSetLayoutBinding textureBinding = {}; textureBinding.binding = 0; textureBinding.type = static_cast(DescriptorType::SRV); textureBinding.count = 1; textureBinding.visibility = static_cast(ShaderVisibility::Pixel); DescriptorSetLayoutDesc textureSetLayout = {}; textureSetLayout.bindings = &textureBinding; textureSetLayout.bindingCount = 1; DescriptorPoolDesc texturePoolDesc = {}; texturePoolDesc.type = DescriptorHeapType::CBV_SRV_UAV; texturePoolDesc.descriptorCount = 1; texturePoolDesc.shaderVisible = true; g_texturePool = g_device.CreateDescriptorPool(texturePoolDesc); if (g_texturePool == nullptr) { Log("[ERROR] Failed to create texture descriptor pool"); return false; } g_textureSet = g_texturePool->AllocateSet(textureSetLayout); if (g_textureSet == nullptr) { Log("[ERROR] Failed to allocate texture descriptor set"); return false; } g_textureSet->Update(0, g_textureView); DescriptorSetLayoutBinding samplerBinding = {}; samplerBinding.binding = 0; samplerBinding.type = static_cast(DescriptorType::Sampler); samplerBinding.count = 1; samplerBinding.visibility = static_cast(ShaderVisibility::Pixel); DescriptorSetLayoutDesc samplerSetLayout = {}; samplerSetLayout.bindings = &samplerBinding; samplerSetLayout.bindingCount = 1; DescriptorPoolDesc samplerPoolDesc = {}; samplerPoolDesc.type = DescriptorHeapType::Sampler; samplerPoolDesc.descriptorCount = 1; samplerPoolDesc.shaderVisible = true; g_samplerPool = g_device.CreateDescriptorPool(samplerPoolDesc); if (g_samplerPool == nullptr) { Log("[ERROR] Failed to create sampler descriptor pool"); return false; } g_samplerSet = g_samplerPool->AllocateSet(samplerSetLayout); if (g_samplerSet == nullptr) { Log("[ERROR] Failed to allocate sampler descriptor set"); return false; } g_samplerSet->UpdateSampler(0, g_sampler); DescriptorSetLayoutDesc setLayouts[] = { textureSetLayout, samplerSetLayout, }; RHIPipelineLayoutDesc pipelineLayoutDesc = {}; pipelineLayoutDesc.setLayouts = setLayouts; pipelineLayoutDesc.setLayoutCount = static_cast(std::size(setLayouts)); g_pipelineLayout = g_device.CreatePipelineLayout(pipelineLayoutDesc); if (g_pipelineLayout == nullptr) { Log("[ERROR] Failed to create pipeline layout"); return false; } return true; } bool InitQuadResources() { BufferDesc vertexBufferDesc = {}; vertexBufferDesc.size = sizeof(kQuadVertices); vertexBufferDesc.stride = sizeof(Vertex); vertexBufferDesc.bufferType = static_cast(BufferType::Vertex); g_vertexBuffer = g_device.CreateBuffer(vertexBufferDesc); if (g_vertexBuffer == nullptr) { Log("[ERROR] Failed to create vertex buffer"); return false; } g_vertexBuffer->SetData(kQuadVertices, sizeof(kQuadVertices)); g_vertexBuffer->SetStride(sizeof(Vertex)); g_vertexBuffer->SetBufferType(BufferType::Vertex); ResourceViewDesc vertexViewDesc = {}; vertexViewDesc.dimension = ResourceViewDimension::Buffer; vertexViewDesc.structureByteStride = sizeof(Vertex); g_vertexBufferView = g_device.CreateVertexBufferView(g_vertexBuffer, vertexViewDesc); if (g_vertexBufferView == nullptr) { Log("[ERROR] Failed to create vertex buffer view"); return false; } BufferDesc indexBufferDesc = {}; indexBufferDesc.size = sizeof(kQuadIndices); indexBufferDesc.stride = sizeof(uint32_t); indexBufferDesc.bufferType = static_cast(BufferType::Index); g_indexBuffer = g_device.CreateBuffer(indexBufferDesc); if (g_indexBuffer == nullptr) { Log("[ERROR] Failed to create index buffer"); return false; } g_indexBuffer->SetData(kQuadIndices, sizeof(kQuadIndices)); g_indexBuffer->SetStride(sizeof(uint32_t)); g_indexBuffer->SetBufferType(BufferType::Index); ResourceViewDesc indexViewDesc = {}; indexViewDesc.dimension = ResourceViewDimension::Buffer; indexViewDesc.format = static_cast(Format::R32_UInt); g_indexBufferView = g_device.CreateIndexBufferView(g_indexBuffer, indexViewDesc); if (g_indexBufferView == nullptr) { Log("[ERROR] Failed to create index buffer view"); return false; } if (!LoadQuadTexture()) { return false; } GraphicsPipelineDesc pipelineDesc = CreateQuadPipelineDesc(); g_pipelineState = g_device.CreatePipelineState(pipelineDesc); if (g_pipelineState == nullptr || !g_pipelineState->IsValid()) { Log("[ERROR] Failed to create quad pipeline state"); return false; } Log("[INFO] Quad resources 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(0.0f, 0.0f, 1.0f, 1.0f, 1); g_commandList.SetPipelineState(g_pipelineState); RHIDescriptorSet* descriptorSets[] = {g_textureSet, g_samplerSet}; g_commandList.SetGraphicsDescriptorSets(0, static_cast(std::size(descriptorSets)), descriptorSets, g_pipelineLayout); g_commandList.SetPrimitiveTopology(PrimitiveTopology::TriangleList); RHIResourceView* vertexBuffers[] = {g_vertexBufferView}; uint64_t offsets[] = {0}; uint32_t strides[] = {sizeof(Vertex)}; g_commandList.SetVertexBuffers(0, 1, vertexBuffers, offsets, strides); g_commandList.SetIndexBuffer(g_indexBufferView, 0); g_commandList.DrawIndexed(static_cast(std::size(kQuadIndices))); 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_Quad_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_Quad_Test", L"Vulkan Quad 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() || !InitQuadResources()) { 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, "quad.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; }