#include #include #include #include #include #include #include #include #include "XCEngine/Core/Containers/String.h" #include "XCEngine/Core/Math/Matrix4.h" #include "XCEngine/Core/Math/Vector3.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::Math; using namespace XCEngine::RHI; namespace { constexpr int kWidth = 1280; constexpr int kHeight = 720; constexpr int kTargetFrameCount = 30; constexpr float kSphereRadius = 1.0f; constexpr int kSphereSegments = 32; constexpr float kPi = 3.14159265358979323846f; constexpr uint32_t kSphereDescriptorFirstSet = 1; constexpr uint32_t kSphereDescriptorSetCount = 4; struct Vertex { float pos[4]; float uv[2]; }; struct MatrixBufferData { Matrix4x4 projection; Matrix4x4 view; Matrix4x4 model; }; const char kSphereVertexShader[] = R"(#version 450 layout(location = 0) in vec4 aPosition; layout(location = 1) in vec2 aTexCoord; layout(set = 1, binding = 0, std140) uniform MatrixBuffer { mat4 gProjectionMatrix; mat4 gViewMatrix; mat4 gModelMatrix; }; layout(location = 0) out vec2 vTexCoord; void main() { vec4 positionWS = gModelMatrix * aPosition; vec4 positionVS = gViewMatrix * positionWS; gl_Position = gProjectionMatrix * positionVS; vTexCoord = aTexCoord; } )"; const char kSphereFragmentShader[] = R"(#version 450 layout(set = 2, binding = 0) uniform texture2D uTexture; layout(set = 3, 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; std::vector g_vertices; std::vector g_indices; RHIBuffer* g_vertexBuffer = nullptr; RHIResourceView* g_vertexBufferView = nullptr; RHIBuffer* g_indexBuffer = nullptr; RHIResourceView* g_indexBufferView = nullptr; RHITexture* g_depthTexture = nullptr; RHIResourceView* g_depthView = nullptr; RHITexture* g_texture = nullptr; RHIResourceView* g_textureView = nullptr; RHISampler* g_sampler = nullptr; RHIDescriptorPool* g_constantPool = nullptr; RHIDescriptorSet* g_constantSet = 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); } } void GenerateSphere(std::vector& vertices, std::vector& indices, float radius, int segments) { vertices.clear(); indices.clear(); segments = segments < 3 ? 3 : segments; for (int lat = 0; lat <= segments; ++lat) { const float phi = kPi * static_cast(lat) / static_cast(segments); const float sinPhi = std::sinf(phi); const float cosPhi = std::cosf(phi); for (int lon = 0; lon <= segments; ++lon) { const float theta = (kPi * 2.0f) * static_cast(lon) / static_cast(segments); const float sinTheta = std::sinf(theta); const float cosTheta = std::cosf(theta); Vertex vertex = {}; vertex.pos[0] = radius * sinPhi * cosTheta; vertex.pos[1] = radius * cosPhi; vertex.pos[2] = radius * sinPhi * sinTheta; vertex.pos[3] = 1.0f; vertex.uv[0] = static_cast(lon) / static_cast(segments); vertex.uv[1] = static_cast(lat) / static_cast(segments); vertices.push_back(vertex); } } for (int lat = 0; lat < segments; ++lat) { for (int lon = 0; lon < segments; ++lon) { const uint32_t topLeft = static_cast(lat * (segments + 1) + lon); const uint32_t topRight = topLeft + 1; const uint32_t bottomLeft = static_cast((lat + 1) * (segments + 1) + lon); const uint32_t bottomRight = bottomLeft + 1; indices.push_back(topLeft); indices.push_back(bottomLeft); indices.push_back(topRight); indices.push_back(topRight); indices.push_back(bottomLeft); indices.push_back(bottomRight); } } } MatrixBufferData CreateMatrixBufferData() { const float aspect = static_cast(kWidth) / static_cast(kHeight); const Matrix4x4 projection = Matrix4x4::Perspective(45.0f * 3.141592f / 180.0f, aspect, 0.1f, 1000.0f); const Matrix4x4 view = Matrix4x4::Identity(); const Matrix4x4 model = Matrix4x4::Translation(Vector3(0.0f, 0.0f, 5.0f)); MatrixBufferData data = {}; data.projection = projection.Transpose(); data.view = view.Transpose(); data.model = model.Transpose(); return data; } GraphicsPipelineDesc CreateSpherePipelineDesc() { 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::D24_UNorm_S8_UInt); 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 = true; desc.depthStencilState.depthWriteEnable = true; desc.depthStencilState.depthFunc = static_cast(ComparisonFunc::Less); 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(kSphereVertexShader, kSphereVertexShader + std::strlen(kSphereVertexShader)); desc.vertexShader.sourceLanguage = ShaderLanguage::GLSL; desc.vertexShader.profile = L"vs"; desc.fragmentShader.source.assign(kSphereFragmentShader, kSphereFragmentShader + std::strlen(kSphereFragmentShader)); 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 ShutdownSphereResources() { ShutdownAndDelete(g_pipelineState); ShutdownAndDelete(g_pipelineLayout); ShutdownAndDelete(g_constantSet); ShutdownAndDelete(g_textureSet); ShutdownAndDelete(g_samplerSet); ShutdownAndDelete(g_constantPool); ShutdownAndDelete(g_texturePool); ShutdownAndDelete(g_samplerPool); ShutdownAndDelete(g_sampler); ShutdownAndDelete(g_textureView); ShutdownAndDelete(g_texture); ShutdownAndDelete(g_depthView); ShutdownAndDelete(g_depthTexture); ShutdownAndDelete(g_vertexBufferView); ShutdownAndDelete(g_indexBufferView); ShutdownAndDelete(g_vertexBuffer); ShutdownAndDelete(g_indexBuffer); g_vertices.clear(); g_indices.clear(); } void ShutdownVulkan() { ShutdownSphereResources(); 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 LoadSphereTexture() { 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 constantBinding = {}; constantBinding.binding = 0; constantBinding.type = static_cast(DescriptorType::CBV); constantBinding.count = 1; constantBinding.visibility = static_cast(ShaderVisibility::Vertex); DescriptorSetLayoutDesc constantLayoutDesc = {}; constantLayoutDesc.bindings = &constantBinding; constantLayoutDesc.bindingCount = 1; DescriptorPoolDesc constantPoolDesc = {}; constantPoolDesc.type = DescriptorHeapType::CBV_SRV_UAV; constantPoolDesc.descriptorCount = 1; constantPoolDesc.shaderVisible = false; g_constantPool = g_device.CreateDescriptorPool(constantPoolDesc); if (g_constantPool == nullptr) { Log("[ERROR] Failed to create constant descriptor pool"); return false; } g_constantSet = g_constantPool->AllocateSet(constantLayoutDesc); if (g_constantSet == nullptr) { Log("[ERROR] Failed to allocate constant descriptor set"); return false; } const MatrixBufferData matrixData = CreateMatrixBufferData(); g_constantSet->WriteConstant(0, &matrixData, sizeof(matrixData)); DescriptorSetLayoutBinding textureBinding = {}; textureBinding.binding = 0; textureBinding.type = static_cast(DescriptorType::SRV); textureBinding.count = 1; textureBinding.visibility = static_cast(ShaderVisibility::Pixel); DescriptorSetLayoutDesc textureLayoutDesc = {}; textureLayoutDesc.bindings = &textureBinding; textureLayoutDesc.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(textureLayoutDesc); 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 samplerLayoutDesc = {}; samplerLayoutDesc.bindings = &samplerBinding; samplerLayoutDesc.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(samplerLayoutDesc); if (g_samplerSet == nullptr) { Log("[ERROR] Failed to allocate sampler descriptor set"); return false; } g_samplerSet->UpdateSampler(0, g_sampler); DescriptorSetLayoutBinding reservedSetBindings[3] = {}; reservedSetBindings[0].binding = 0; reservedSetBindings[0].type = static_cast(DescriptorType::CBV); reservedSetBindings[0].count = 1; reservedSetBindings[1].binding = 0; reservedSetBindings[1].type = static_cast(DescriptorType::SRV); reservedSetBindings[1].count = 1; reservedSetBindings[2].binding = 0; reservedSetBindings[2].type = static_cast(DescriptorType::Sampler); reservedSetBindings[2].count = 1; DescriptorSetLayoutDesc reservedLayoutDesc = {}; reservedLayoutDesc.bindings = reservedSetBindings; reservedLayoutDesc.bindingCount = 3; DescriptorSetLayoutDesc setLayouts[kSphereDescriptorSetCount] = {}; setLayouts[0] = reservedLayoutDesc; setLayouts[1] = constantLayoutDesc; setLayouts[2] = textureLayoutDesc; setLayouts[3] = samplerLayoutDesc; RHIPipelineLayoutDesc pipelineLayoutDesc = {}; pipelineLayoutDesc.setLayouts = setLayouts; pipelineLayoutDesc.setLayoutCount = kSphereDescriptorSetCount; g_pipelineLayout = g_device.CreatePipelineLayout(pipelineLayoutDesc); if (g_pipelineLayout == nullptr) { Log("[ERROR] Failed to create pipeline layout"); return false; } return true; } bool InitDepthResources() { TextureDesc depthDesc = {}; depthDesc.width = kWidth; depthDesc.height = kHeight; depthDesc.depth = 1; depthDesc.mipLevels = 1; depthDesc.arraySize = 1; depthDesc.format = static_cast(Format::D24_UNorm_S8_UInt); depthDesc.textureType = static_cast(TextureType::Texture2D); depthDesc.sampleCount = 1; depthDesc.sampleQuality = 0; depthDesc.flags = 0; g_depthTexture = g_device.CreateTexture(depthDesc); if (g_depthTexture == nullptr) { Log("[ERROR] Failed to create depth texture"); return false; } ResourceViewDesc depthViewDesc = {}; depthViewDesc.format = static_cast(Format::D24_UNorm_S8_UInt); depthViewDesc.dimension = ResourceViewDimension::Texture2D; depthViewDesc.mipLevel = 0; depthViewDesc.arraySize = 1; g_depthView = g_device.CreateDepthStencilView(g_depthTexture, depthViewDesc); if (g_depthView == nullptr) { Log("[ERROR] Failed to create depth view"); return false; } return true; } bool InitSphereResources() { GenerateSphere(g_vertices, g_indices, kSphereRadius, kSphereSegments); if (g_vertices.empty() || g_indices.empty()) { Log("[ERROR] Failed to generate sphere geometry"); return false; } BufferDesc vertexBufferDesc = {}; vertexBufferDesc.size = static_cast(g_vertices.size() * sizeof(Vertex)); 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(g_vertices.data(), g_vertices.size() * sizeof(Vertex)); 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 = static_cast(g_indices.size() * sizeof(uint32_t)); 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(g_indices.data(), g_indices.size() * sizeof(uint32_t)); 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 (!InitDepthResources()) { return false; } if (!LoadSphereTexture()) { return false; } GraphicsPipelineDesc pipelineDesc = CreateSpherePipelineDesc(); g_pipelineState = g_device.CreatePipelineState(pipelineDesc); if (g_pipelineState == nullptr || !g_pipelineState->IsValid()) { Log("[ERROR] Failed to create sphere pipeline state"); return false; } Log("[INFO] Sphere 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 || g_depthView == nullptr) { return false; } g_commandList.Reset(); g_commandList.SetRenderTargets(1, &renderTargetView, g_depthView); 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 | 2); g_commandList.SetPipelineState(g_pipelineState); RHIDescriptorSet* descriptorSets[] = {g_constantSet, g_textureSet, g_samplerSet}; g_commandList.SetGraphicsDescriptorSets( kSphereDescriptorFirstSet, static_cast(sizeof(descriptorSets) / sizeof(descriptorSets[0])), 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(g_indices.size())); 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_Sphere_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_Sphere_Test", L"Vulkan Sphere 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() || !InitSphereResources()) { 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, "sphere.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; }