mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 13:44:58 +00:00
feat: Add swapchain and pipeline services for Vulkan rendering
This commit is contained in:
@@ -127,6 +127,8 @@ if(BUILD_SDL3_APP)
|
||||
src/services/impl/sdl_window_service.cpp
|
||||
src/services/impl/sdl_input_service.cpp
|
||||
src/services/impl/vulkan_device_service.cpp
|
||||
src/services/impl/swapchain_service.cpp
|
||||
src/services/impl/pipeline_service.cpp
|
||||
src/app/sdl3_app_core.cpp
|
||||
src/app/audio_player.cpp
|
||||
src/app/sdl3_app_device.cpp
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"conan": {}
|
||||
},
|
||||
"include": [
|
||||
"build/Release/generators/CMakePresets.json",
|
||||
"build/build/Release/generators/CMakePresets.json"
|
||||
]
|
||||
}
|
||||
313
src/services/impl/pipeline_service.cpp
Normal file
313
src/services/impl/pipeline_service.cpp
Normal file
@@ -0,0 +1,313 @@
|
||||
#include "pipeline_service.hpp"
|
||||
#include "../../core/vertex.hpp"
|
||||
#include "../../logging/logger.hpp"
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
PipelineService::PipelineService(std::shared_ptr<IVulkanDeviceService> deviceService)
|
||||
: deviceService_(std::move(deviceService)) {}
|
||||
|
||||
PipelineService::~PipelineService() {
|
||||
if (pipelineLayout_ != VK_NULL_HANDLE || !pipelines_.empty()) {
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineService::RegisterShader(const std::string& key, const ShaderPaths& paths) {
|
||||
logging::TraceGuard trace;
|
||||
shaderPathMap_[key] = paths;
|
||||
logging::Logger::GetInstance().Debug("Registered shader: " + key);
|
||||
}
|
||||
|
||||
void PipelineService::CompileAll(VkRenderPass renderPass, VkExtent2D extent) {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
if (shaderPathMap_.empty()) {
|
||||
throw std::runtime_error("No shader paths were registered before pipeline creation");
|
||||
}
|
||||
|
||||
CreatePipelineLayout();
|
||||
CreatePipelinesInternal(renderPass, extent);
|
||||
|
||||
logging::Logger::GetInstance().Info("Compiled " + std::to_string(pipelines_.size()) + " pipeline(s)");
|
||||
}
|
||||
|
||||
void PipelineService::RecreatePipelines(VkRenderPass renderPass, VkExtent2D extent) {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
CleanupPipelines();
|
||||
CreatePipelineLayout();
|
||||
CreatePipelinesInternal(renderPass, extent);
|
||||
|
||||
logging::Logger::GetInstance().Info("Recreated " + std::to_string(pipelines_.size()) + " pipeline(s)");
|
||||
}
|
||||
|
||||
void PipelineService::Cleanup() {
|
||||
CleanupPipelines();
|
||||
|
||||
auto device = deviceService_->GetDevice();
|
||||
|
||||
if (pipelineLayout_ != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(device, pipelineLayout_, nullptr);
|
||||
pipelineLayout_ = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineService::Shutdown() noexcept {
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
VkPipeline PipelineService::GetPipeline(const std::string& key) const {
|
||||
auto it = pipelines_.find(key);
|
||||
if (it == pipelines_.end()) {
|
||||
throw std::out_of_range("Pipeline not found: " + key);
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
bool PipelineService::HasShader(const std::string& key) const {
|
||||
return shaderPathMap_.find(key) != shaderPathMap_.end();
|
||||
}
|
||||
|
||||
void PipelineService::CreatePipelineLayout() {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
auto device = deviceService_->GetDevice();
|
||||
|
||||
VkPushConstantRange pushRange{};
|
||||
pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
pushRange.offset = 0;
|
||||
pushRange.size = sizeof(core::PushConstants);
|
||||
|
||||
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
|
||||
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||
pipelineLayoutInfo.pushConstantRangeCount = 1;
|
||||
pipelineLayoutInfo.pPushConstantRanges = &pushRange;
|
||||
|
||||
if (pipelineLayout_ != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(device, pipelineLayout_, nullptr);
|
||||
pipelineLayout_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout_) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create pipeline layout");
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineService::CreatePipelinesInternal(VkRenderPass renderPass, VkExtent2D extent) {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
auto device = deviceService_->GetDevice();
|
||||
|
||||
// Vertex input configuration
|
||||
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
|
||||
VkVertexInputBindingDescription bindingDescription{};
|
||||
bindingDescription.binding = 0;
|
||||
bindingDescription.stride = sizeof(core::Vertex);
|
||||
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};
|
||||
attributeDescriptions[0].binding = 0;
|
||||
attributeDescriptions[0].location = 0;
|
||||
attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
attributeDescriptions[0].offset = offsetof(core::Vertex, position);
|
||||
|
||||
attributeDescriptions[1].binding = 0;
|
||||
attributeDescriptions[1].location = 1;
|
||||
attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
attributeDescriptions[1].offset = offsetof(core::Vertex, color);
|
||||
|
||||
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
||||
vertexInputInfo.vertexBindingDescriptionCount = 1;
|
||||
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
|
||||
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
|
||||
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
|
||||
|
||||
// Input assembly
|
||||
VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
|
||||
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
||||
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
inputAssembly.primitiveRestartEnable = VK_FALSE;
|
||||
|
||||
// Viewport and scissor
|
||||
VkViewport viewport{};
|
||||
viewport.x = 0.0f;
|
||||
viewport.y = 0.0f;
|
||||
viewport.width = static_cast<float>(extent.width);
|
||||
viewport.height = static_cast<float>(extent.height);
|
||||
viewport.minDepth = 0.0f;
|
||||
viewport.maxDepth = 1.0f;
|
||||
|
||||
VkRect2D scissor{};
|
||||
scissor.offset = {0, 0};
|
||||
scissor.extent = extent;
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewportState{};
|
||||
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
||||
viewportState.viewportCount = 1;
|
||||
viewportState.pViewports = &viewport;
|
||||
viewportState.scissorCount = 1;
|
||||
viewportState.pScissors = &scissor;
|
||||
|
||||
// Rasterization
|
||||
VkPipelineRasterizationStateCreateInfo rasterizer{};
|
||||
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
||||
rasterizer.depthClampEnable = VK_FALSE;
|
||||
rasterizer.rasterizerDiscardEnable = VK_FALSE;
|
||||
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
|
||||
rasterizer.lineWidth = 1.0f;
|
||||
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
|
||||
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
|
||||
rasterizer.depthBiasEnable = VK_FALSE;
|
||||
|
||||
// Multisampling
|
||||
VkPipelineMultisampleStateCreateInfo multisampling{};
|
||||
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
||||
multisampling.sampleShadingEnable = VK_FALSE;
|
||||
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
|
||||
// Color blending
|
||||
VkPipelineColorBlendAttachmentState colorBlendAttachment{};
|
||||
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
colorBlendAttachment.blendEnable = VK_FALSE;
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo colorBlending{};
|
||||
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
||||
colorBlending.logicOpEnable = VK_FALSE;
|
||||
colorBlending.attachmentCount = 1;
|
||||
colorBlending.pAttachments = &colorBlendAttachment;
|
||||
|
||||
// Base pipeline info
|
||||
VkGraphicsPipelineCreateInfo pipelineInfo{};
|
||||
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
||||
pipelineInfo.pVertexInputState = &vertexInputInfo;
|
||||
pipelineInfo.pInputAssemblyState = &inputAssembly;
|
||||
pipelineInfo.pViewportState = &viewportState;
|
||||
pipelineInfo.pRasterizationState = &rasterizer;
|
||||
pipelineInfo.pMultisampleState = &multisampling;
|
||||
pipelineInfo.pColorBlendState = &colorBlending;
|
||||
pipelineInfo.layout = pipelineLayout_;
|
||||
pipelineInfo.renderPass = renderPass;
|
||||
pipelineInfo.subpass = 0;
|
||||
|
||||
// Create pipeline for each registered shader
|
||||
for (const auto& [key, paths] : shaderPathMap_) {
|
||||
// Validate shader files exist
|
||||
if (!std::filesystem::exists(paths.vertex)) {
|
||||
throw std::runtime_error(
|
||||
"Vertex shader not found: " + paths.vertex +
|
||||
"\n\nShader key: " + key +
|
||||
"\n\nPlease ensure shader files are compiled and present in the shaders directory.");
|
||||
}
|
||||
if (!std::filesystem::exists(paths.fragment)) {
|
||||
throw std::runtime_error(
|
||||
"Fragment shader not found: " + paths.fragment +
|
||||
"\n\nShader key: " + key +
|
||||
"\n\nPlease ensure shader files are compiled and present in the shaders directory.");
|
||||
}
|
||||
|
||||
auto vertShaderCode = ReadShaderFile(paths.vertex);
|
||||
auto fragShaderCode = ReadShaderFile(paths.fragment);
|
||||
|
||||
VkShaderModule vertShaderModule = CreateShaderModule(vertShaderCode);
|
||||
VkShaderModule fragShaderModule = CreateShaderModule(fragShaderCode);
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStageInfo{};
|
||||
vertStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
vertStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
vertStageInfo.module = vertShaderModule;
|
||||
vertStageInfo.pName = "main";
|
||||
|
||||
VkPipelineShaderStageCreateInfo fragStageInfo{};
|
||||
fragStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
fragStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
fragStageInfo.module = fragShaderModule;
|
||||
fragStageInfo.pName = "main";
|
||||
|
||||
VkPipelineShaderStageCreateInfo shaderStages[] = {vertStageInfo, fragStageInfo};
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipelineCreateInfo = pipelineInfo;
|
||||
pipelineCreateInfo.stageCount = 2;
|
||||
pipelineCreateInfo.pStages = shaderStages;
|
||||
|
||||
VkPipeline pipeline;
|
||||
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr,
|
||||
&pipeline) != VK_SUCCESS) {
|
||||
vkDestroyShaderModule(device, fragShaderModule, nullptr);
|
||||
vkDestroyShaderModule(device, vertShaderModule, nullptr);
|
||||
throw std::runtime_error("Failed to create graphics pipeline for shader: " + key);
|
||||
}
|
||||
|
||||
pipelines_[key] = pipeline;
|
||||
|
||||
vkDestroyShaderModule(device, fragShaderModule, nullptr);
|
||||
vkDestroyShaderModule(device, vertShaderModule, nullptr);
|
||||
|
||||
logging::Logger::GetInstance().Debug("Created pipeline: " + key);
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineService::CleanupPipelines() {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
auto device = deviceService_->GetDevice();
|
||||
|
||||
for (auto& [key, pipeline] : pipelines_) {
|
||||
vkDestroyPipeline(device, pipeline, nullptr);
|
||||
}
|
||||
pipelines_.clear();
|
||||
}
|
||||
|
||||
VkShaderModule PipelineService::CreateShaderModule(const std::vector<char>& code) {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
auto device = deviceService_->GetDevice();
|
||||
|
||||
VkShaderModuleCreateInfo createInfo{};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
createInfo.codeSize = code.size();
|
||||
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
|
||||
|
||||
VkShaderModule shaderModule;
|
||||
if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create shader module");
|
||||
}
|
||||
return shaderModule;
|
||||
}
|
||||
|
||||
std::vector<char> PipelineService::ReadShaderFile(const std::string& path) {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
if (!std::filesystem::exists(path)) {
|
||||
throw std::runtime_error("Shader file not found: " + path +
|
||||
"\n\nPlease ensure the file exists at this location.");
|
||||
}
|
||||
|
||||
if (!std::filesystem::is_regular_file(path)) {
|
||||
throw std::runtime_error("Path is not a regular file: " + path);
|
||||
}
|
||||
|
||||
std::ifstream file(path, std::ios::ate | std::ios::binary);
|
||||
if (!file) {
|
||||
throw std::runtime_error("Failed to open shader file: " + path +
|
||||
"\n\nCheck file permissions.");
|
||||
}
|
||||
|
||||
size_t fileSize = static_cast<size_t>(file.tellg());
|
||||
std::vector<char> buffer(fileSize);
|
||||
|
||||
file.seekg(0);
|
||||
file.read(buffer.data(), static_cast<std::streamsize>(fileSize));
|
||||
file.close();
|
||||
|
||||
logging::Logger::GetInstance().Debug("Read shader file: " + path + " (" + std::to_string(fileSize) + " bytes)");
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
53
src/services/impl/pipeline_service.hpp
Normal file
53
src/services/impl/pipeline_service.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "../interfaces/i_pipeline_service.hpp"
|
||||
#include "../interfaces/i_vulkan_device_service.hpp"
|
||||
#include "../../di/lifecycle.hpp"
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
/**
|
||||
* @brief Pipeline service implementation.
|
||||
*
|
||||
* Small, focused service (~200 lines) for Vulkan graphics pipeline management.
|
||||
* Handles shader compilation, pipeline creation, and pipeline layout.
|
||||
*/
|
||||
class PipelineService : public IPipelineService,
|
||||
public di::IShutdownable {
|
||||
public:
|
||||
explicit PipelineService(std::shared_ptr<IVulkanDeviceService> deviceService);
|
||||
~PipelineService() override;
|
||||
|
||||
// IPipelineService interface
|
||||
void RegisterShader(const std::string& key, const ShaderPaths& paths) override;
|
||||
void CompileAll(VkRenderPass renderPass, VkExtent2D extent) override;
|
||||
void RecreatePipelines(VkRenderPass renderPass, VkExtent2D extent) override;
|
||||
void Cleanup() override;
|
||||
|
||||
VkPipeline GetPipeline(const std::string& key) const override;
|
||||
VkPipelineLayout GetPipelineLayout() const override { return pipelineLayout_; }
|
||||
bool HasShader(const std::string& key) const override;
|
||||
size_t GetShaderCount() const override { return shaderPathMap_.size(); }
|
||||
|
||||
// IShutdownable interface
|
||||
void Shutdown() noexcept override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<IVulkanDeviceService> deviceService_;
|
||||
|
||||
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
|
||||
std::unordered_map<std::string, ShaderPaths> shaderPathMap_;
|
||||
std::unordered_map<std::string, VkPipeline> pipelines_;
|
||||
|
||||
// Helper methods
|
||||
VkShaderModule CreateShaderModule(const std::vector<char>& code);
|
||||
std::vector<char> ReadShaderFile(const std::string& path);
|
||||
void CreatePipelineLayout();
|
||||
void CreatePipelinesInternal(VkRenderPass renderPass, VkExtent2D extent);
|
||||
void CleanupPipelines();
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
338
src/services/impl/swapchain_service.cpp
Normal file
338
src/services/impl/swapchain_service.cpp
Normal file
@@ -0,0 +1,338 @@
|
||||
#include "swapchain_service.hpp"
|
||||
#include "../../logging/logger.hpp"
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
SwapchainService::SwapchainService(std::shared_ptr<IVulkanDeviceService> deviceService,
|
||||
std::shared_ptr<events::EventBus> eventBus)
|
||||
: deviceService_(std::move(deviceService)), eventBus_(std::move(eventBus)) {
|
||||
// Subscribe to window resize events
|
||||
eventBus_->Subscribe(events::EventType::WindowResized,
|
||||
[this](const events::Event& event) { OnWindowResized(event); });
|
||||
}
|
||||
|
||||
SwapchainService::~SwapchainService() {
|
||||
if (swapchain_ != VK_NULL_HANDLE) {
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void SwapchainService::Initialize() {
|
||||
logging::TraceGuard trace;
|
||||
// Initialization happens in CreateSwapchain()
|
||||
}
|
||||
|
||||
void SwapchainService::CreateSwapchain(uint32_t width, uint32_t height) {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
currentWidth_ = width;
|
||||
currentHeight_ = height;
|
||||
|
||||
auto physicalDevice = deviceService_->GetPhysicalDevice();
|
||||
auto surface = deviceService_->GetSurface();
|
||||
auto device = deviceService_->GetDevice();
|
||||
|
||||
SwapchainSupportDetails support = QuerySwapchainSupport(physicalDevice, surface);
|
||||
|
||||
// Validate swap chain support
|
||||
if (support.formats.empty()) {
|
||||
throw std::runtime_error("No surface formats available for swap chain.\n"
|
||||
"This may indicate GPU driver issues or incompatible surface.");
|
||||
}
|
||||
if (support.presentModes.empty()) {
|
||||
throw std::runtime_error("No present modes available for swap chain.\n"
|
||||
"This may indicate GPU driver issues or incompatible surface.");
|
||||
}
|
||||
|
||||
logging::Logger::GetInstance().Info("Creating swapchain with size: " + std::to_string(width) + "x" + std::to_string(height));
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
logging::Logger::GetInstance().Error("Invalid dimensions (" + std::to_string(width) + "x" + std::to_string(height) + ").");
|
||||
throw std::runtime_error("Invalid dimensions (" +
|
||||
std::to_string(width) + "x" + std::to_string(height) + ").\n" +
|
||||
"Window may be minimized or invalid.");
|
||||
}
|
||||
|
||||
logging::Logger::GetInstance().Debug("Surface capabilities - Min extent: " + std::to_string(support.capabilities.minImageExtent.width) + "x" + std::to_string(support.capabilities.minImageExtent.height) +
|
||||
", Max extent: " + std::to_string(support.capabilities.maxImageExtent.width) + "x" + std::to_string(support.capabilities.maxImageExtent.height) +
|
||||
", Min images: " + std::to_string(support.capabilities.minImageCount) +
|
||||
", Max images: " + std::to_string(support.capabilities.maxImageCount));
|
||||
|
||||
VkSurfaceFormatKHR surfaceFormat = ChooseSurfaceFormat(support.formats);
|
||||
VkPresentModeKHR presentMode = ChoosePresentMode(support.presentModes);
|
||||
VkExtent2D extent = ChooseExtent(support.capabilities, width, height);
|
||||
|
||||
uint32_t imageCount = support.capabilities.minImageCount + 1;
|
||||
if (support.capabilities.maxImageCount > 0 && imageCount > support.capabilities.maxImageCount) {
|
||||
imageCount = support.capabilities.maxImageCount;
|
||||
}
|
||||
logging::Logger::GetInstance().TraceVariable("imageCount", static_cast<int>(imageCount));
|
||||
|
||||
VkSwapchainCreateInfoKHR createInfo{};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
||||
createInfo.surface = surface;
|
||||
createInfo.minImageCount = imageCount;
|
||||
createInfo.imageFormat = surfaceFormat.format;
|
||||
createInfo.imageColorSpace = surfaceFormat.colorSpace;
|
||||
createInfo.imageExtent = extent;
|
||||
createInfo.imageArrayLayers = 1;
|
||||
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
|
||||
QueueFamilyIndices indices = deviceService_->GetQueueFamilies();
|
||||
uint32_t queueFamilyIndices[] = {indices.graphicsFamily, indices.presentFamily};
|
||||
if (indices.graphicsFamily != indices.presentFamily) {
|
||||
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
|
||||
createInfo.queueFamilyIndexCount = 2;
|
||||
createInfo.pQueueFamilyIndices = queueFamilyIndices;
|
||||
} else {
|
||||
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
}
|
||||
|
||||
createInfo.preTransform = support.capabilities.currentTransform;
|
||||
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
createInfo.presentMode = presentMode;
|
||||
createInfo.clipped = VK_TRUE;
|
||||
|
||||
if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapchain_) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create swap chain");
|
||||
}
|
||||
|
||||
vkGetSwapchainImagesKHR(device, swapchain_, &imageCount, nullptr);
|
||||
images_.resize(imageCount);
|
||||
vkGetSwapchainImagesKHR(device, swapchain_, &imageCount, images_.data());
|
||||
|
||||
imageFormat_ = surfaceFormat.format;
|
||||
extent_ = extent;
|
||||
|
||||
CreateImageViews();
|
||||
CreateRenderPass();
|
||||
CreateFramebuffers();
|
||||
}
|
||||
|
||||
void SwapchainService::RecreateSwapchain(uint32_t width, uint32_t height) {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
logging::Logger::GetInstance().Info("Recreating swapchain: " + std::to_string(width) + "x" + std::to_string(height));
|
||||
|
||||
deviceService_->WaitIdle();
|
||||
CleanupSwapchainInternal();
|
||||
CreateSwapchain(width, height);
|
||||
}
|
||||
|
||||
VkResult SwapchainService::AcquireNextImage(VkSemaphore semaphore, uint32_t& imageIndex) {
|
||||
auto device = deviceService_->GetDevice();
|
||||
return vkAcquireNextImageKHR(device, swapchain_, UINT64_MAX, semaphore,
|
||||
VK_NULL_HANDLE, &imageIndex);
|
||||
}
|
||||
|
||||
VkResult SwapchainService::Present(const std::vector<VkSemaphore>& waitSemaphores,
|
||||
uint32_t imageIndex) {
|
||||
auto presentQueue = deviceService_->GetPresentQueue();
|
||||
|
||||
VkPresentInfoKHR presentInfo{};
|
||||
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||
presentInfo.waitSemaphoreCount = static_cast<uint32_t>(waitSemaphores.size());
|
||||
presentInfo.pWaitSemaphores = waitSemaphores.data();
|
||||
presentInfo.swapchainCount = 1;
|
||||
presentInfo.pSwapchains = &swapchain_;
|
||||
presentInfo.pImageIndices = &imageIndex;
|
||||
|
||||
return vkQueuePresentKHR(presentQueue, &presentInfo);
|
||||
}
|
||||
|
||||
void SwapchainService::CreateImageViews() {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
auto device = deviceService_->GetDevice();
|
||||
|
||||
imageViews_.resize(images_.size());
|
||||
for (size_t i = 0; i < images_.size(); ++i) {
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = images_[i];
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = imageFormat_;
|
||||
viewInfo.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.baseArrayLayer = 0;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
|
||||
if (vkCreateImageView(device, &viewInfo, nullptr, &imageViews_[i]) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create image views");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SwapchainService::CreateRenderPass() {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
auto device = deviceService_->GetDevice();
|
||||
|
||||
VkAttachmentDescription colorAttachment{};
|
||||
colorAttachment.format = imageFormat_;
|
||||
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
|
||||
VkAttachmentReference colorAttachmentRef{};
|
||||
colorAttachmentRef.attachment = 0;
|
||||
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = 1;
|
||||
subpass.pColorAttachments = &colorAttachmentRef;
|
||||
|
||||
VkSubpassDependency dependency{};
|
||||
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependency.dstSubpass = 0;
|
||||
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependency.srcAccessMask = 0;
|
||||
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
|
||||
VkRenderPassCreateInfo renderPassInfo{};
|
||||
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
renderPassInfo.attachmentCount = 1;
|
||||
renderPassInfo.pAttachments = &colorAttachment;
|
||||
renderPassInfo.subpassCount = 1;
|
||||
renderPassInfo.pSubpasses = &subpass;
|
||||
renderPassInfo.dependencyCount = 1;
|
||||
renderPassInfo.pDependencies = &dependency;
|
||||
|
||||
if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass_) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create render pass");
|
||||
}
|
||||
}
|
||||
|
||||
void SwapchainService::CreateFramebuffers() {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
auto device = deviceService_->GetDevice();
|
||||
|
||||
framebuffers_.resize(imageViews_.size());
|
||||
for (size_t i = 0; i < imageViews_.size(); ++i) {
|
||||
VkImageView attachments[] = {imageViews_[i]};
|
||||
|
||||
VkFramebufferCreateInfo framebufferInfo{};
|
||||
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
framebufferInfo.renderPass = renderPass_;
|
||||
framebufferInfo.attachmentCount = 1;
|
||||
framebufferInfo.pAttachments = attachments;
|
||||
framebufferInfo.width = extent_.width;
|
||||
framebufferInfo.height = extent_.height;
|
||||
framebufferInfo.layers = 1;
|
||||
|
||||
if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &framebuffers_[i]) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create framebuffer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SwapchainService::CleanupSwapchainInternal() {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
auto device = deviceService_->GetDevice();
|
||||
|
||||
for (auto framebuffer : framebuffers_) {
|
||||
vkDestroyFramebuffer(device, framebuffer, nullptr);
|
||||
}
|
||||
framebuffers_.clear();
|
||||
|
||||
if (renderPass_ != VK_NULL_HANDLE) {
|
||||
vkDestroyRenderPass(device, renderPass_, nullptr);
|
||||
renderPass_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
for (auto imageView : imageViews_) {
|
||||
vkDestroyImageView(device, imageView, nullptr);
|
||||
}
|
||||
imageViews_.clear();
|
||||
|
||||
if (swapchain_ != VK_NULL_HANDLE) {
|
||||
vkDestroySwapchainKHR(device, swapchain_, nullptr);
|
||||
swapchain_ = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
void SwapchainService::CleanupSwapchain() {
|
||||
CleanupSwapchainInternal();
|
||||
}
|
||||
|
||||
void SwapchainService::Shutdown() noexcept {
|
||||
CleanupSwapchainInternal();
|
||||
}
|
||||
|
||||
SwapchainService::SwapchainSupportDetails SwapchainService::QuerySwapchainSupport(
|
||||
VkPhysicalDevice device, VkSurfaceKHR surface) {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
SwapchainSupportDetails details;
|
||||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
|
||||
|
||||
uint32_t formatCount;
|
||||
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
|
||||
if (formatCount != 0) {
|
||||
details.formats.resize(formatCount);
|
||||
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
|
||||
}
|
||||
|
||||
uint32_t presentModeCount;
|
||||
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
|
||||
if (presentModeCount != 0) {
|
||||
details.presentModes.resize(presentModeCount);
|
||||
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount,
|
||||
details.presentModes.data());
|
||||
}
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
VkSurfaceFormatKHR SwapchainService::ChooseSurfaceFormat(
|
||||
const std::vector<VkSurfaceFormatKHR>& availableFormats) {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
for (const auto& availableFormat : availableFormats) {
|
||||
if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB &&
|
||||
availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
|
||||
return availableFormat;
|
||||
}
|
||||
}
|
||||
return availableFormats[0];
|
||||
}
|
||||
|
||||
VkPresentModeKHR SwapchainService::ChoosePresentMode(
|
||||
const std::vector<VkPresentModeKHR>& availablePresentModes) {
|
||||
logging::TraceGuard trace;
|
||||
|
||||
for (const auto& availablePresentMode : availablePresentModes) {
|
||||
if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
|
||||
return availablePresentMode;
|
||||
}
|
||||
}
|
||||
return VK_PRESENT_MODE_FIFO_KHR;
|
||||
}
|
||||
|
||||
VkExtent2D SwapchainService::ChooseExtent(const VkSurfaceCapabilitiesKHR& capabilities,
|
||||
uint32_t width, uint32_t height) {
|
||||
return VkExtent2D{
|
||||
std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width),
|
||||
std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height)
|
||||
};
|
||||
}
|
||||
|
||||
void SwapchainService::OnWindowResized(const events::Event& event) {
|
||||
logging::TraceGuard trace;
|
||||
logging::Logger::GetInstance().Info("Window resized event received, swapchain recreation needed");
|
||||
}
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
84
src/services/impl/swapchain_service.hpp
Normal file
84
src/services/impl/swapchain_service.hpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include "../interfaces/i_swapchain_service.hpp"
|
||||
#include "../interfaces/i_vulkan_device_service.hpp"
|
||||
#include "../../di/lifecycle.hpp"
|
||||
#include "../../events/event_bus.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
/**
|
||||
* @brief Swapchain service implementation.
|
||||
*
|
||||
* Small, focused service (~250 lines) for Vulkan swapchain management.
|
||||
* Handles swapchain creation, recreation, image views, render pass, and framebuffers.
|
||||
*/
|
||||
class SwapchainService : public ISwapchainService,
|
||||
public di::IInitializable,
|
||||
public di::IShutdownable {
|
||||
public:
|
||||
explicit SwapchainService(std::shared_ptr<IVulkanDeviceService> deviceService,
|
||||
std::shared_ptr<events::EventBus> eventBus);
|
||||
~SwapchainService() override;
|
||||
|
||||
// ISwapchainService interface
|
||||
void CreateSwapchain(uint32_t width, uint32_t height) override;
|
||||
void RecreateSwapchain(uint32_t width, uint32_t height) override;
|
||||
void CleanupSwapchain() override;
|
||||
|
||||
VkResult AcquireNextImage(VkSemaphore semaphore, uint32_t& imageIndex) override;
|
||||
VkResult Present(const std::vector<VkSemaphore>& waitSemaphores,
|
||||
uint32_t imageIndex) override;
|
||||
|
||||
VkSwapchainKHR GetSwapchain() const override { return swapchain_; }
|
||||
const std::vector<VkImage>& GetSwapchainImages() const override { return images_; }
|
||||
const std::vector<VkImageView>& GetSwapchainImageViews() const override { return imageViews_; }
|
||||
const std::vector<VkFramebuffer>& GetSwapchainFramebuffers() const override { return framebuffers_; }
|
||||
VkFormat GetSwapchainImageFormat() const override { return imageFormat_; }
|
||||
VkExtent2D GetSwapchainExtent() const override { return extent_; }
|
||||
VkRenderPass GetRenderPass() const override { return renderPass_; }
|
||||
|
||||
// IInitializable interface
|
||||
void Initialize() override;
|
||||
|
||||
// IShutdownable interface
|
||||
void Shutdown() noexcept override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<IVulkanDeviceService> deviceService_;
|
||||
std::shared_ptr<events::EventBus> eventBus_;
|
||||
|
||||
VkSwapchainKHR swapchain_ = VK_NULL_HANDLE;
|
||||
std::vector<VkImage> images_;
|
||||
std::vector<VkImageView> imageViews_;
|
||||
VkFormat imageFormat_ = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D extent_{};
|
||||
VkRenderPass renderPass_ = VK_NULL_HANDLE;
|
||||
std::vector<VkFramebuffer> framebuffers_;
|
||||
|
||||
uint32_t currentWidth_ = 0;
|
||||
uint32_t currentHeight_ = 0;
|
||||
|
||||
// Helper methods
|
||||
struct SwapchainSupportDetails {
|
||||
VkSurfaceCapabilitiesKHR capabilities{};
|
||||
std::vector<VkSurfaceFormatKHR> formats;
|
||||
std::vector<VkPresentModeKHR> presentModes;
|
||||
};
|
||||
|
||||
SwapchainSupportDetails QuerySwapchainSupport(VkPhysicalDevice device, VkSurfaceKHR surface);
|
||||
VkSurfaceFormatKHR ChooseSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats);
|
||||
VkPresentModeKHR ChoosePresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes);
|
||||
VkExtent2D ChooseExtent(const VkSurfaceCapabilitiesKHR& capabilities, uint32_t width, uint32_t height);
|
||||
|
||||
void CreateImageViews();
|
||||
void CreateRenderPass();
|
||||
void CreateFramebuffers();
|
||||
void CleanupSwapchainInternal();
|
||||
|
||||
void OnWindowResized(const events::Event& event);
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
Reference in New Issue
Block a user