#include "render_command_service.hpp" #include "../../core/vertex.hpp" #include #include namespace sdl3cpp::services::impl { RenderCommandService::RenderCommandService(std::shared_ptr deviceService, std::shared_ptr swapchainService, std::shared_ptr pipelineService, std::shared_ptr bufferService, std::shared_ptr configService, std::shared_ptr logger) : deviceService_(std::move(deviceService)), swapchainService_(std::move(swapchainService)), pipelineService_(std::move(pipelineService)), bufferService_(std::move(bufferService)), configService_(std::move(configService)), logger_(logger) { if (logger_) { logger_->Trace("RenderCommandService", "RenderCommandService", "deviceService=" + std::string(deviceService_ ? "set" : "null") + ", swapchainService=" + std::string(swapchainService_ ? "set" : "null") + ", pipelineService=" + std::string(pipelineService_ ? "set" : "null") + ", bufferService=" + std::string(bufferService_ ? "set" : "null")); } if (!deviceService_ || !swapchainService_ || !pipelineService_ || !bufferService_) { throw std::invalid_argument("All render command dependencies must be provided"); } } RenderCommandService::~RenderCommandService() { if (logger_) { logger_->Trace("RenderCommandService", "~RenderCommandService"); } if (commandPool_ != VK_NULL_HANDLE || imageAvailableSemaphore_ != VK_NULL_HANDLE) { Shutdown(); } } void RenderCommandService::Cleanup() { logger_->Trace("RenderCommandService", "Cleanup"); CleanupCommandResources(); CleanupSyncObjects(); } void RenderCommandService::Shutdown() noexcept { logger_->Trace("RenderCommandService", "Shutdown"); Cleanup(); } bool RenderCommandService::BeginFrame(uint32_t& imageIndex) { logger_->Trace("RenderCommandService", "BeginFrame", "imageIndex=" + std::to_string(imageIndex)); // Lazy initialization if (commandPool_ == VK_NULL_HANDLE) { CreateCommandPool(); CreateCommandBuffers(); CreateSyncObjects(); logger_->Info("RenderCommandService initialized lazily"); } auto device = deviceService_->GetDevice(); // Wait for previous frame (with timeout) constexpr uint64_t kFenceTimeout = 5000000000ULL; // 5 seconds VkResult fenceResult = vkWaitForFences(device, 1, &inFlightFence_, VK_TRUE, kFenceTimeout); if (fenceResult == VK_TIMEOUT) { logger_->Error("Fence wait timeout: GPU appears to be hung"); throw std::runtime_error("Fence wait timeout: GPU appears to be hung"); } else if (fenceResult != VK_SUCCESS) { logger_->Error("Fence wait failed"); throw std::runtime_error("Fence wait failed"); } vkResetFences(device, 1, &inFlightFence_); // Acquire next image VkResult result = swapchainService_->AcquireNextImage(imageAvailableSemaphore_, imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { return false; // Need swapchain recreation } else if (result == VK_TIMEOUT) { logger_->Error("Image acquisition timeout"); throw std::runtime_error("Image acquisition timeout: GPU appears to be hung"); } else if (result != VK_SUCCESS) { throw std::runtime_error("Failed to acquire swap chain image"); } return true; } void RenderCommandService::RecordCommands(uint32_t imageIndex, const std::vector& commands, const std::array& viewProj) { logger_->Trace("RenderCommandService", "RecordCommands", "imageIndex=" + std::to_string(imageIndex) + ", commands.size=" + std::to_string(commands.size()) + ", viewProj.size=" + std::to_string(viewProj.size())); VkCommandBuffer commandBuffer = commandBuffers_[imageIndex]; // Reset command buffer vkResetCommandBuffer(commandBuffer, 0); // Begin command buffer VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; vkBeginCommandBuffer(commandBuffer, &beginInfo); // Begin render pass auto framebuffers = swapchainService_->GetSwapchainFramebuffers(); auto extent = swapchainService_->GetSwapchainExtent(); auto renderPass = swapchainService_->GetRenderPass(); VkRenderPassBeginInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.renderPass = renderPass; renderPassInfo.framebuffer = framebuffers[imageIndex]; renderPassInfo.renderArea.offset = {0, 0}; renderPassInfo.renderArea.extent = extent; std::array clearValues{}; clearValues[0].color = {{0.1f, 0.1f, 0.15f, 1.0f}}; // Color clear clearValues[1].depthStencil = {1.0f, 0}; // Depth clear renderPassInfo.clearValueCount = static_cast(clearValues.size()); renderPassInfo.pClearValues = clearValues.data(); vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); if (commands.empty()) { logger_->Trace("RenderCommandService", "RecordCommands", "No render commands to draw; skipping buffer bind"); } else { VkBuffer vertexBuffer = bufferService_->GetVertexBuffer(); VkBuffer indexBuffer = bufferService_->GetIndexBuffer(); if (vertexBuffer == VK_NULL_HANDLE || indexBuffer == VK_NULL_HANDLE) { logger_->Error("RenderCommandService: Vertex or index buffer not initialized"); } else { VkDeviceSize offsets[] = {0}; vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer, offsets); vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); VkPipelineLayout pipelineLayout = pipelineService_->GetPipelineLayout(); if (pipelineLayout == VK_NULL_HANDLE) { logger_->Error("RenderCommandService: Pipeline layout is not initialized"); } else { if (logger_) { logger_->Trace("RenderCommandService", "RecordCommands", "drawing commands=" + std::to_string(commands.size())); } size_t drawIndex = 0; for (const auto& command : commands) { if (!pipelineService_->HasShader(command.shaderKey)) { logger_->Error("RenderCommandService: Missing pipeline for shader key: " + command.shaderKey); continue; } if (logger_) { logger_->Trace("RenderCommandService", "RecordCommands", "draw=" + std::to_string(drawIndex) + ", shaderKey=" + command.shaderKey + ", indexOffset=" + std::to_string(command.indexOffset) + ", indexCount=" + std::to_string(command.indexCount) + ", vertexOffset=" + std::to_string(command.vertexOffset)); } VkPipeline pipeline = pipelineService_->GetPipeline(command.shaderKey); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); core::PushConstants pushConstants{}; pushConstants.model = command.modelMatrix; pushConstants.viewProj = viewProj; // For PBR shaders, populate extended push constants if (command.shaderKey.find("pbr") != std::string::npos) { // Get atmospherics config auto config = configService_->GetConfig(); // For now, use identity for view and proj (since viewProj is already combined) // In a full implementation, we'd need separate view/proj matrices pushConstants.view = {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; pushConstants.proj = pushConstants.view; // Identity for now pushConstants.lightViewProj = pushConstants.view; // Identity for now // Camera position (0,0,0) for now - would need to be passed from scene pushConstants.cameraPos = {0.0f, 0.0f, 0.0f}; pushConstants.time = 0.0f; // Would need actual time // Atmospherics pushConstants.ambientStrength = config.atmospherics.ambientStrength; pushConstants.fogDensity = config.atmospherics.fogDensity; pushConstants.fogStart = 0.0f; pushConstants.fogEnd = 100.0f; pushConstants.fogColor = config.atmospherics.fogColor; pushConstants.gamma = config.atmospherics.gamma; pushConstants.exposure = 1.0f; pushConstants.enableShadows = config.atmospherics.enableShadows ? 1 : 0; pushConstants.enableFog = 1; // Enable fog for PBR } vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(core::PushConstants), &pushConstants); vkCmdDrawIndexed(commandBuffer, command.indexCount, 1, command.indexOffset, command.vertexOffset, 0); ++drawIndex; } } } } vkCmdEndRenderPass(commandBuffer); vkEndCommandBuffer(commandBuffer); } bool RenderCommandService::EndFrame(uint32_t imageIndex) { logger_->Trace("RenderCommandService", "EndFrame", "imageIndex=" + std::to_string(imageIndex)); auto device = deviceService_->GetDevice(); auto graphicsQueue = deviceService_->GetGraphicsQueue(); VkCommandBuffer commandBuffer = commandBuffers_[imageIndex]; // Submit command buffer VkSemaphore waitSemaphores[] = {imageAvailableSemaphore_}; VkSemaphore signalSemaphores[] = {renderFinishedSemaphore_}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence_) != VK_SUCCESS) { throw std::runtime_error("Failed to submit draw command buffer"); } // Present std::vector presentWaitSemaphores = {renderFinishedSemaphore_}; VkResult result = swapchainService_->Present(presentWaitSemaphores, imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { return false; // Need swapchain recreation } else if (result != VK_SUCCESS) { throw std::runtime_error("Failed to present swap chain image"); } return true; } VkCommandBuffer RenderCommandService::GetCurrentCommandBuffer() const { logger_->Trace("RenderCommandService", "GetCurrentCommandBuffer"); if (commandBuffers_.empty()) { return VK_NULL_HANDLE; } return commandBuffers_[currentFrame_]; } void RenderCommandService::CreateCommandPool() { logger_->Trace("RenderCommandService", "CreateCommandPool"); auto device = deviceService_->GetDevice(); auto queueFamilies = deviceService_->GetQueueFamilies(); VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilies.graphicsFamily; poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool_) != VK_SUCCESS) { throw std::runtime_error("Failed to create command pool"); } } void RenderCommandService::CreateCommandBuffers() { logger_->Trace("RenderCommandService", "CreateCommandBuffers"); auto device = deviceService_->GetDevice(); auto framebuffers = swapchainService_->GetSwapchainFramebuffers(); commandBuffers_.resize(framebuffers.size()); VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool_; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandBufferCount = static_cast(commandBuffers_.size()); if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers_.data()) != VK_SUCCESS) { throw std::runtime_error("Failed to allocate command buffers"); } logger_->Debug("Created " + std::to_string(commandBuffers_.size()) + " command buffers"); } void RenderCommandService::CreateSyncObjects() { logger_->Trace("RenderCommandService", "CreateSyncObjects"); auto device = deviceService_->GetDevice(); VkSemaphoreCreateInfo semaphoreInfo{}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; VkFenceCreateInfo fenceInfo{}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; // Start signaled if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore_) != VK_SUCCESS || vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore_) != VK_SUCCESS || vkCreateFence(device, &fenceInfo, nullptr, &inFlightFence_) != VK_SUCCESS) { throw std::runtime_error("Failed to create synchronization objects"); } logger_->Debug("Created synchronization objects"); } void RenderCommandService::CleanupCommandResources() { logger_->Trace("RenderCommandService", "CleanupCommandResources"); auto device = deviceService_->GetDevice(); if (!commandBuffers_.empty() && commandPool_ != VK_NULL_HANDLE) { vkFreeCommandBuffers(device, commandPool_, static_cast(commandBuffers_.size()), commandBuffers_.data()); commandBuffers_.clear(); } if (commandPool_ != VK_NULL_HANDLE) { vkDestroyCommandPool(device, commandPool_, nullptr); commandPool_ = VK_NULL_HANDLE; } } void RenderCommandService::CleanupSyncObjects() { logger_->Trace("RenderCommandService", "CleanupSyncObjects"); auto device = deviceService_->GetDevice(); if (imageAvailableSemaphore_ != VK_NULL_HANDLE) { vkDestroySemaphore(device, imageAvailableSemaphore_, nullptr); imageAvailableSemaphore_ = VK_NULL_HANDLE; } if (renderFinishedSemaphore_ != VK_NULL_HANDLE) { vkDestroySemaphore(device, renderFinishedSemaphore_, nullptr); renderFinishedSemaphore_ = VK_NULL_HANDLE; } if (inFlightFence_ != VK_NULL_HANDLE) { vkDestroyFence(device, inFlightFence_, nullptr); inFlightFence_ = VK_NULL_HANDLE; } } } // namespace sdl3cpp::services::impl