#include "vulkan_graphics_backend.hpp" #include "../../core/vertex.hpp" #include #include #include #include #include namespace sdl3cpp::services::impl { VulkanGraphicsBackend::VulkanGraphicsBackend(std::shared_ptr deviceService, std::shared_ptr swapchainService, std::shared_ptr renderCommandService, std::shared_ptr pipelineService, std::shared_ptr bufferService, std::shared_ptr logger) : deviceService_(std::move(deviceService)), swapchainService_(std::move(swapchainService)), renderCommandService_(std::move(renderCommandService)), pipelineService_(std::move(pipelineService)), bufferService_(std::move(bufferService)), logger_(std::move(logger)), window_(nullptr), initialized_(false), currentImageIndex_(0), frameCommands_(), currentViewProj_{} { logger_->Trace("VulkanGraphicsBackend", "VulkanGraphicsBackend", "deviceService=" + std::string(deviceService_ ? "set" : "null") + ", swapchainService=" + std::string(swapchainService_ ? "set" : "null") + ", renderCommandService=" + std::string(renderCommandService_ ? "set" : "null") + ", pipelineService=" + std::string(pipelineService_ ? "set" : "null") + ", bufferService=" + std::string(bufferService_ ? "set" : "null")); } VulkanGraphicsBackend::~VulkanGraphicsBackend() { logger_->Trace("VulkanGraphicsBackend", "~VulkanGraphicsBackend"); if (initialized_) { Shutdown(); } } void VulkanGraphicsBackend::Initialize(void* window, const GraphicsConfig& config) { logger_->Trace("VulkanGraphicsBackend", "Initialize"); if (initialized_) return; window_ = static_cast(window); // Initialize Vulkan device std::vector deviceExtensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; deviceService_->Initialize(deviceExtensions, config.enableValidationLayers); deviceService_->CreateSurface(window_); deviceService_->CreateLogicalDevice(); // Get window size for swapchain int width, height; SDL_GetWindowSizeInPixels(window_, &width, &height); // Initialize swapchain swapchainService_->CreateSwapchain(static_cast(width), static_cast(height)); // Initialize render command service (this creates command pools/buffers) // Note: RenderCommandService initialization happens in its constructor initialized_ = true; } void VulkanGraphicsBackend::Shutdown() { logger_->Trace("VulkanGraphicsBackend", "Shutdown"); if (!initialized_) return; // Cleanup in reverse order renderCommandService_->Cleanup(); pipelineService_->Cleanup(); bufferService_->Cleanup(); swapchainService_->CleanupSwapchain(); deviceService_->Shutdown(); initialized_ = false; } void VulkanGraphicsBackend::RecreateSwapchain(uint32_t width, uint32_t height) { logger_->Trace("VulkanGraphicsBackend", "RecreateSwapchain", "width=" + std::to_string(width) + ", height=" + std::to_string(height)); if (!initialized_) { return; } if (width == 0 || height == 0) { logger_->Warn("VulkanGraphicsBackend::RecreateSwapchain: Skipping swapchain recreation for zero size"); return; } deviceService_->WaitIdle(); swapchainService_->RecreateSwapchain(width, height); VkExtent2D extent = swapchainService_->GetSwapchainExtent(); pipelineService_->RecreatePipelines(swapchainService_->GetRenderPass(), extent); renderCommandService_->OnSwapchainRecreated(); } void VulkanGraphicsBackend::WaitIdle() { logger_->Trace("VulkanGraphicsBackend", "WaitIdle"); deviceService_->WaitIdle(); } GraphicsDeviceHandle VulkanGraphicsBackend::CreateDevice() { logger_->Trace("VulkanGraphicsBackend", "CreateDevice"); // Device is already created in Initialize, just return a handle return reinterpret_cast(deviceService_->GetDevice()); } void VulkanGraphicsBackend::DestroyDevice(GraphicsDeviceHandle device) { logger_->Trace("VulkanGraphicsBackend", "DestroyDevice"); // Device cleanup happens in Shutdown } GraphicsPipelineHandle VulkanGraphicsBackend::CreatePipeline(GraphicsDeviceHandle device, const std::string& shaderKey, const ShaderPaths& shaderPaths) { logger_->Trace("VulkanGraphicsBackend", "CreatePipeline", "shaderKey=" + shaderKey); // Register shader with pipeline service pipelineService_->RegisterShader(shaderKey, shaderPaths); // Compile pipeline with render pass from swapchain service // Note: This assumes swapchain service has created the render pass VkExtent2D extent = swapchainService_->GetSwapchainExtent(); if (logger_) { logger_->Trace("VulkanGraphicsBackend", "CreatePipeline", "swapchainExtent=" + std::to_string(extent.width) + "x" + std::to_string(extent.height)); } pipelineService_->CompileAll(swapchainService_->GetRenderPass(), extent); // Return the pipeline handle VkPipeline pipeline = pipelineService_->GetPipeline(shaderKey); GraphicsPipelineHandle handle = reinterpret_cast(pipeline); // Store the mapping from handle to key pipelineToShaderKey_[handle] = shaderKey; return handle; } void VulkanGraphicsBackend::DestroyPipeline(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline) { logger_->Trace("VulkanGraphicsBackend", "DestroyPipeline"); // Pipeline cleanup happens in pipeline service Shutdown } GraphicsBufferHandle VulkanGraphicsBackend::CreateVertexBuffer(GraphicsDeviceHandle device, const std::vector& data) { logger_->Trace("VulkanGraphicsBackend", "CreateVertexBuffer", "data.size=" + std::to_string(data.size())); if (data.empty()) { logger_->Error("VulkanGraphicsBackend::CreateVertexBuffer: No vertex data provided"); return nullptr; } if (data.size() % sizeof(core::Vertex) != 0) { logger_->Error("VulkanGraphicsBackend::CreateVertexBuffer: Vertex data size is not aligned to Vertex"); return nullptr; } const size_t vertexCount = data.size() / sizeof(core::Vertex); if (logger_) { logger_->Trace("VulkanGraphicsBackend", "CreateVertexBuffer", "vertexCount=" + std::to_string(vertexCount)); } std::vector vertices(vertexCount); std::memcpy(vertices.data(), data.data(), data.size()); bufferService_->UploadVertexData(vertices); return reinterpret_cast(bufferService_->GetVertexBuffer()); } GraphicsBufferHandle VulkanGraphicsBackend::CreateIndexBuffer(GraphicsDeviceHandle device, const std::vector& data) { logger_->Trace("VulkanGraphicsBackend", "CreateIndexBuffer", "data.size=" + std::to_string(data.size())); if (data.empty()) { logger_->Error("VulkanGraphicsBackend::CreateIndexBuffer: No index data provided"); return nullptr; } if (data.size() % sizeof(uint16_t) != 0) { logger_->Error("VulkanGraphicsBackend::CreateIndexBuffer: Index data size is not aligned to uint16_t"); return nullptr; } const size_t indexCount = data.size() / sizeof(uint16_t); if (logger_) { logger_->Trace("VulkanGraphicsBackend", "CreateIndexBuffer", "indexCount=" + std::to_string(indexCount)); } std::vector indices(indexCount); std::memcpy(indices.data(), data.data(), data.size()); bufferService_->UploadIndexData(indices); return reinterpret_cast(bufferService_->GetIndexBuffer()); } void VulkanGraphicsBackend::DestroyBuffer(GraphicsDeviceHandle device, GraphicsBufferHandle buffer) { logger_->Trace("VulkanGraphicsBackend", "DestroyBuffer"); // Buffer cleanup happens in buffer service Shutdown } bool VulkanGraphicsBackend::BeginFrame(GraphicsDeviceHandle device) { logger_->Trace("VulkanGraphicsBackend", "BeginFrame"); frameCommands_.clear(); return renderCommandService_->BeginFrame(currentImageIndex_); } bool VulkanGraphicsBackend::EndFrame(GraphicsDeviceHandle device) { logger_->Trace("VulkanGraphicsBackend", "EndFrame"); // Record all accumulated commands if (renderGraphEnabled_) { renderCommandService_->RecordRenderGraph(currentImageIndex_, renderGraphDefinition_, frameCommands_, currentViewProj_); } else { renderCommandService_->RecordCommands(currentImageIndex_, frameCommands_, currentViewProj_); } // End the frame return renderCommandService_->EndFrame(currentImageIndex_); } void VulkanGraphicsBackend::SetViewProjection(const std::array& viewProj) { logger_->Trace("VulkanGraphicsBackend", "SetViewProjection"); currentViewProj_ = viewProj; } void VulkanGraphicsBackend::SetRenderGraphDefinition(const RenderGraphDefinition& definition) { logger_->Trace("VulkanGraphicsBackend", "SetRenderGraphDefinition", "resources=" + std::to_string(definition.resources.size()) + ", passes=" + std::to_string(definition.passes.size())); renderGraphDefinition_ = definition; renderGraphEnabled_ = !definition.passes.empty(); } void VulkanGraphicsBackend::Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline, GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer, uint32_t indexOffset, uint32_t indexCount, int32_t vertexOffset, const std::array& modelMatrix) { logger_->Trace("VulkanGraphicsBackend", "Draw", "indexOffset=" + std::to_string(indexOffset) + ", indexCount=" + std::to_string(indexCount) + ", vertexOffset=" + std::to_string(vertexOffset)); // Find the shader key for this pipeline auto it = pipelineToShaderKey_.find(pipeline); if (it == pipelineToShaderKey_.end()) { logger_->Error("VulkanGraphicsBackend::Draw: Pipeline not found in mapping"); return; } // Create a render command RenderCommand command; command.indexOffset = indexOffset; command.indexCount = indexCount; command.vertexOffset = vertexOffset; command.shaderKey = it->second; command.modelMatrix = modelMatrix; // Accumulate the command for later recording frameCommands_.push_back(command); } GraphicsDeviceHandle VulkanGraphicsBackend::GetPhysicalDevice() const { logger_->Trace("VulkanGraphicsBackend", "GetPhysicalDevice"); return reinterpret_cast(deviceService_->GetPhysicalDevice()); } std::pair VulkanGraphicsBackend::GetSwapchainExtent() const { logger_->Trace("VulkanGraphicsBackend", "GetSwapchainExtent"); VkExtent2D extent = swapchainService_->GetSwapchainExtent(); return {extent.width, extent.height}; } uint32_t VulkanGraphicsBackend::GetSwapchainFormat() const { logger_->Trace("VulkanGraphicsBackend", "GetSwapchainFormat"); return static_cast(swapchainService_->GetSwapchainImageFormat()); } void* VulkanGraphicsBackend::GetCurrentCommandBuffer() const { logger_->Trace("VulkanGraphicsBackend", "GetCurrentCommandBuffer"); return reinterpret_cast(renderCommandService_->GetCurrentCommandBuffer()); } void* VulkanGraphicsBackend::GetGraphicsQueue() const { logger_->Trace("VulkanGraphicsBackend", "GetGraphicsQueue"); return reinterpret_cast(deviceService_->GetGraphicsQueue()); } } // namespace sdl3cpp::services::impl