feat: 搭建 Vulkan 1.4 渲染器骨架,绘制旋转 3D 立方体
使用 Vulkan 1.4 现代路径初始化引擎骨架: - 用 Shader Object (VK_EXT_shader_object) 替代传统 VkPipeline - 用 Dynamic Rendering 替代 RenderPass/Framebuffer - 用 Synchronization2 管理 barrier 核心模块: - Window:GLFW 封装,GLFW_NO_API 模式,兼容 Wayland - Log:spdlog 封装 - Types:u32/u64/f32 类型别名 Vulkan 后端: - Device:Instance、PhysicalDevice、LogicalDevice、Queue 及 feature 查询 - Swapchain:Image、ImageView、DepthBuffer、Recreate - ShaderManager:SPIR-V 加载与 ShaderEXT 创建 - CommandManager:CommandPool 与主 CommandBuffer - SyncManager:per-frame Fence 与 ImageAvailable semaphore,per-image RenderFinished semaphore,2 frames in flight - MemoryAllocator:内存子分配器,256MB block,persistent mapping - Buffer:VertexBuffer、IndexBuffer、UniformBuffer 封装 - UniformBuffer:每帧上传 MVP 矩阵 - DescriptorManager:DescriptorSetLayout、DescriptorPool、DescriptorSet 及更新 Renderer: - 协调所有子系统 - 绘制每面不同颜色的旋转 3D 立方体 - 启用 depth test,MVP 矩阵由 GLM 计算 构建: - CMake 组织 Engine 静态库与 App 可执行文件 - glfw、glm、spdlog 通过 add_subdirectory 引入
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/20.
|
||||
//
|
||||
|
||||
#include <Engine/Vulkan/Buffer.h>
|
||||
#include <Engine/Vulkan/Device.h>
|
||||
#include <Engine/Vulkan/MemoryAllocator.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace Engine {
|
||||
|
||||
Buffer::Buffer(Device& device, MemoryAllocator& allocator,
|
||||
vk::DeviceSize size, vk::BufferUsageFlags usage,
|
||||
vk::MemoryPropertyFlags memProps)
|
||||
: m_Device(device), m_Size(size)
|
||||
{
|
||||
vk::BufferCreateInfo bufferInfo;
|
||||
bufferInfo.setSize(size)
|
||||
.setUsage(usage)
|
||||
.setSharingMode(vk::SharingMode::eExclusive);
|
||||
|
||||
m_Buffer = m_Device.GetDevice().createBuffer(bufferInfo);
|
||||
|
||||
auto reqs = m_Device.GetDevice().getBufferMemoryRequirements(m_Buffer);
|
||||
auto alloc = allocator.Allocate(reqs, memProps);
|
||||
|
||||
m_Memory = alloc.Memory;
|
||||
m_Offset = alloc.Offset;
|
||||
m_MappedPtr = alloc.MappedPtr;
|
||||
|
||||
m_Device.GetDevice().bindBufferMemory(m_Buffer, m_Memory, m_Offset);
|
||||
}
|
||||
|
||||
|
||||
Buffer::~Buffer()
|
||||
{
|
||||
m_Device.GetDevice().destroyBuffer(m_Buffer);
|
||||
}
|
||||
|
||||
void Buffer::Upload(const void* data, vk::DeviceSize size, vk::DeviceSize offset)
|
||||
{
|
||||
if (!m_MappedPtr) {
|
||||
ENGINE_ERROR("Buffer::Upload: buffer is not host visible");
|
||||
return;
|
||||
}
|
||||
memcpy(static_cast<char*>(m_MappedPtr) + offset, data, size);
|
||||
|
||||
// flush 整个内存块,避免对齐问题
|
||||
vk::MappedMemoryRange range;
|
||||
range.setMemory(m_Memory)
|
||||
.setOffset(0)
|
||||
.setSize(VK_WHOLE_SIZE);
|
||||
|
||||
m_Device.GetDevice().flushMappedMemoryRanges(range);
|
||||
}
|
||||
} // namespace Engine
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/20.
|
||||
//
|
||||
|
||||
#include <Engine/Vulkan/CommandManager.h>
|
||||
#include <Engine/Vulkan/Device.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
|
||||
namespace Engine {
|
||||
|
||||
CommandManager::CommandManager(Device& device)
|
||||
: m_Device(device)
|
||||
{
|
||||
// 创建命令池
|
||||
vk::CommandPoolCreateInfo poolInfo;
|
||||
poolInfo.setQueueFamilyIndex(m_Device.GetGraphicsFamily())
|
||||
.setFlags(vk::CommandPoolCreateFlagBits::eResetCommandBuffer);
|
||||
|
||||
m_Pool = m_Device.GetDevice().createCommandPool(poolInfo);
|
||||
|
||||
// 分配一个命令缓冲
|
||||
vk::CommandBufferAllocateInfo allocInfo;
|
||||
allocInfo.setCommandPool(m_Pool)
|
||||
.setLevel(vk::CommandBufferLevel::ePrimary)
|
||||
.setCommandBufferCount(1);
|
||||
|
||||
m_Buffer = m_Device.GetDevice().allocateCommandBuffers(allocInfo)[0];
|
||||
|
||||
ENGINE_INFO("CommandManager created");
|
||||
}
|
||||
|
||||
CommandManager::~CommandManager()
|
||||
{
|
||||
m_Device.GetDevice().destroyCommandPool(m_Pool);
|
||||
}
|
||||
|
||||
vk::CommandBuffer CommandManager::BeginFrame()
|
||||
{
|
||||
m_Buffer.reset();
|
||||
|
||||
vk::CommandBufferBeginInfo beginInfo;
|
||||
beginInfo.setFlags(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
|
||||
m_Buffer.begin(beginInfo);
|
||||
|
||||
return m_Buffer;
|
||||
}
|
||||
|
||||
void CommandManager::EndFrame()
|
||||
{
|
||||
m_Buffer.end();
|
||||
}
|
||||
|
||||
} // namespace Engine
|
||||
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/20.
|
||||
//
|
||||
|
||||
#include <Engine/Vulkan/DescriptorManager.h>
|
||||
#include <Engine/Vulkan/Device.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
|
||||
namespace Engine
|
||||
{
|
||||
DescriptorManager::DescriptorManager(Device& device)
|
||||
: m_Device(device)
|
||||
{
|
||||
vk::DescriptorPoolSize poolSize;
|
||||
poolSize.setType(vk::DescriptorType::eUniformBuffer)
|
||||
.setDescriptorCount(kMaxSets);
|
||||
|
||||
vk::DescriptorPoolCreateInfo poolInfo;
|
||||
poolInfo.setMaxSets(kMaxSets)
|
||||
.setPoolSizes(poolSize)
|
||||
.setFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet);
|
||||
|
||||
m_Pool = m_Device.GetDevice().createDescriptorPool(poolInfo);
|
||||
|
||||
ENGINE_INFO("DescriptorManager created");
|
||||
}
|
||||
|
||||
DescriptorManager::~DescriptorManager()
|
||||
{
|
||||
m_Device.GetDevice().destroyDescriptorPool(m_Pool);
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout DescriptorManager::CreateUniformBufferLayout(u32 binding)
|
||||
{
|
||||
vk::DescriptorSetLayoutBinding layoutBinding;
|
||||
layoutBinding.setBinding(binding)
|
||||
.setDescriptorType(vk::DescriptorType::eUniformBuffer)
|
||||
.setDescriptorCount(1)
|
||||
.setStageFlags(vk::ShaderStageFlagBits::eVertex |
|
||||
vk::ShaderStageFlagBits::eFragment);
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo layoutInfo;
|
||||
layoutInfo.setBindings(layoutBinding);
|
||||
|
||||
return m_Device.GetDevice().createDescriptorSetLayout(layoutInfo);
|
||||
}
|
||||
|
||||
vk::PipelineLayout DescriptorManager::CreatePipelineLayout(vk::DescriptorSetLayout layout)
|
||||
{
|
||||
vk::PipelineLayoutCreateInfo info;
|
||||
info.setSetLayouts(layout);
|
||||
return m_Device.GetDevice().createPipelineLayout(info);
|
||||
}
|
||||
|
||||
vk::DescriptorSet DescriptorManager::AllocateDescriptorSet(vk::DescriptorSetLayout layout)
|
||||
{
|
||||
vk::DescriptorSetAllocateInfo allocInfo;
|
||||
allocInfo.setDescriptorPool(m_Pool)
|
||||
.setSetLayouts(layout);
|
||||
|
||||
auto sets = m_Device.GetDevice().allocateDescriptorSets(allocInfo);
|
||||
return sets[0];
|
||||
}
|
||||
|
||||
void DescriptorManager::UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,
|
||||
vk::Buffer buffer, u64 size)
|
||||
{
|
||||
vk::DescriptorBufferInfo bufferInfo;
|
||||
bufferInfo.setBuffer(buffer)
|
||||
.setOffset(0)
|
||||
.setRange(size);
|
||||
|
||||
vk::WriteDescriptorSet write;
|
||||
write.setDstSet(set)
|
||||
.setDstBinding(binding)
|
||||
.setDstArrayElement(0)
|
||||
.setDescriptorType(vk::DescriptorType::eUniformBuffer)
|
||||
.setDescriptorCount(1)
|
||||
.setBufferInfo(bufferInfo);
|
||||
|
||||
m_Device.GetDevice().updateDescriptorSets(write, {});
|
||||
}
|
||||
} // namespace Engine
|
||||
@@ -0,0 +1,331 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/20.
|
||||
//
|
||||
|
||||
|
||||
#include <Engine/Vulkan/Device.h>
|
||||
#include <Engine/Core/Window.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <set>
|
||||
|
||||
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
|
||||
|
||||
namespace Engine {
|
||||
|
||||
// ---------- Debug Callback ----------
|
||||
static VKAPI_ATTR vk::Bool32 VKAPI_CALL DebugCallback(
|
||||
vk::DebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||
vk::DebugUtilsMessageTypeFlagsEXT type,
|
||||
const vk::DebugUtilsMessengerCallbackDataEXT* data,
|
||||
void* userData)
|
||||
{
|
||||
if (severity >= vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) {
|
||||
ENGINE_WARN("[Vulkan] {}", data->pMessage);
|
||||
} else {
|
||||
ENGINE_TRACE("[Vulkan] {}", data->pMessage);
|
||||
}
|
||||
return vk::False;
|
||||
}
|
||||
|
||||
// ---------- 构造 / 析构 ----------
|
||||
|
||||
Device::Device(Window& window)
|
||||
{
|
||||
CreateInstance();
|
||||
SetupDebugMessenger();
|
||||
CreateSurface(window);
|
||||
PickPhysicalDevice();
|
||||
CreateLogicalDevice();
|
||||
}
|
||||
|
||||
Device::~Device()
|
||||
{
|
||||
m_Device.destroy();
|
||||
|
||||
if (m_DebugMessenger) {
|
||||
m_Instance.destroyDebugUtilsMessengerEXT(m_DebugMessenger);
|
||||
}
|
||||
|
||||
m_Instance.destroySurfaceKHR(m_Surface);
|
||||
m_Instance.destroy();
|
||||
}
|
||||
|
||||
// ---------- Instance ----------
|
||||
|
||||
void Device::CreateInstance()
|
||||
{
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init();
|
||||
|
||||
if (kEnableValidation && !CheckValidationLayerSupport()) {
|
||||
ENGINE_FATAL("Validation layers requested, but not available");
|
||||
}
|
||||
|
||||
vk::ApplicationInfo appInfo;
|
||||
appInfo.setPApplicationName("HelloVulkan")
|
||||
.setApplicationVersion(VK_MAKE_VERSION(1, 0, 0))
|
||||
.setPEngineName("Engine")
|
||||
.setEngineVersion(VK_MAKE_VERSION(1, 0, 0))
|
||||
.setApiVersion(VK_API_VERSION_1_4); // ← 1.4
|
||||
|
||||
auto extensions = Window::GetRequiredExtensions();
|
||||
if (kEnableValidation) {
|
||||
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
vk::InstanceCreateInfo createInfo;
|
||||
createInfo.setPApplicationInfo(&appInfo)
|
||||
.setPEnabledExtensionNames(extensions);
|
||||
|
||||
if (kEnableValidation) {
|
||||
createInfo.setPEnabledLayerNames(m_ValidationLayers);
|
||||
}
|
||||
|
||||
m_Instance = vk::createInstance(createInfo);
|
||||
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(m_Instance);
|
||||
|
||||
ENGINE_INFO("Vulkan Instance created (API 1.4)");
|
||||
|
||||
}
|
||||
|
||||
void Device::SetupDebugMessenger()
|
||||
{
|
||||
if (!kEnableValidation) return;
|
||||
|
||||
vk::DebugUtilsMessengerCreateInfoEXT createInfo;
|
||||
createInfo.setMessageSeverity(
|
||||
vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
|
||||
vk::DebugUtilsMessageSeverityFlagBitsEXT::eError)
|
||||
.setMessageType(
|
||||
vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
|
||||
vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation |
|
||||
vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance)
|
||||
.setPfnUserCallback(&DebugCallback);
|
||||
|
||||
m_DebugMessenger = m_Instance.createDebugUtilsMessengerEXT(createInfo);
|
||||
}
|
||||
|
||||
// ---------- Surface ----------
|
||||
|
||||
void Device::CreateSurface(Window& window)
|
||||
{
|
||||
m_Surface = window.CreateSurface(m_Instance);
|
||||
ENGINE_INFO("Vulkan Surface created");
|
||||
}
|
||||
|
||||
// ---------- PhysicalDevice ----------
|
||||
|
||||
void Device::PickPhysicalDevice()
|
||||
{
|
||||
auto devices = m_Instance.enumeratePhysicalDevices();
|
||||
if (devices.empty()) {
|
||||
ENGINE_FATAL("No Vulkan-capable GPU found");
|
||||
}
|
||||
|
||||
for (auto& device : devices) {
|
||||
if (IsDeviceSuitable(device)) {
|
||||
m_PhysicalDevice = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_PhysicalDevice) {
|
||||
ENGINE_FATAL("No suitable GPU found");
|
||||
}
|
||||
|
||||
m_MemoryProperties = m_PhysicalDevice.getMemoryProperties();
|
||||
auto props = m_PhysicalDevice.getProperties();
|
||||
ENGINE_INFO("Selected GPU: {}", props.deviceName.data());
|
||||
|
||||
// ========================================================
|
||||
// 查询 1.4 特性
|
||||
//
|
||||
// 关键点:dynamicRendering 和 synchronization2 是 1.3 的特性,
|
||||
// 它们就定义在 VkPhysicalDeviceVulkan13Features 里[reference:2]。
|
||||
// Vulkan 1.4 没有把它们“搬走”,而是在 1.4 的结构体里新增了
|
||||
// dynamicRenderingLocalRead、pushDescriptor 等 1.4 独有的特性[reference:3]。
|
||||
//
|
||||
// 所以查询时,两个结构体都要有,用 pNext 串起来。
|
||||
// ========================================================
|
||||
|
||||
vk::PhysicalDeviceVulkan14Features features14{};
|
||||
vk::PhysicalDeviceVulkan13Features features13{};
|
||||
vk::PhysicalDeviceVulkan12Features features12{};
|
||||
|
||||
// 链接:1.4 → 1.3 → 1.2
|
||||
features14.pNext = &features13;
|
||||
features13.pNext = &features12;
|
||||
|
||||
vk::PhysicalDeviceFeatures2 features2{};
|
||||
features2.pNext = &features14;
|
||||
m_PhysicalDevice.getFeatures2(&features2);
|
||||
|
||||
// 1.3 的特性
|
||||
m_DynamicRenderingSupported = features13.dynamicRendering;
|
||||
m_Synchronization2Supported = features13.synchronization2;
|
||||
|
||||
// 1.4 的新特性
|
||||
bool localReadSupported = features14.dynamicRenderingLocalRead;
|
||||
bool pushDescriptorSupported = features14.pushDescriptor;
|
||||
|
||||
// 查询 Shader Object 扩展
|
||||
auto exts = m_PhysicalDevice.enumerateDeviceExtensionProperties();
|
||||
for (auto& ext : exts) {
|
||||
if (strcmp(ext.extensionName, "VK_EXT_shader_object") == 0) {
|
||||
m_ShaderObjectSupported = true;
|
||||
}
|
||||
}
|
||||
|
||||
ENGINE_INFO(" Dynamic Rendering (1.3): {}", m_DynamicRenderingSupported ? "Yes" : "No");
|
||||
ENGINE_INFO(" Synchronization2 (1.3): {}", m_Synchronization2Supported ? "Yes" : "No");
|
||||
ENGINE_INFO(" Dynamic Rendering Local Read (1.4): {}", localReadSupported ? "Yes" : "No");
|
||||
ENGINE_INFO(" Push Descriptor (1.4): {}", pushDescriptorSupported ? "Yes" : "No");
|
||||
ENGINE_INFO(" Shader Object (EXT): {}", m_ShaderObjectSupported ? "Yes" : "No");
|
||||
}
|
||||
|
||||
bool Device::IsDeviceSuitable(vk::PhysicalDevice device)
|
||||
{
|
||||
auto families = device.getQueueFamilyProperties();
|
||||
bool hasGraphics = false;
|
||||
bool hasPresent = false;
|
||||
|
||||
for (uint32_t i = 0; i < families.size(); ++i) {
|
||||
if (families[i].queueFlags & vk::QueueFlagBits::eGraphics) {
|
||||
hasGraphics = true;
|
||||
}
|
||||
if (device.getSurfaceSupportKHR(i, m_Surface)) {
|
||||
hasPresent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasGraphics || !hasPresent) return false;
|
||||
|
||||
auto exts = device.enumerateDeviceExtensionProperties();
|
||||
std::set<std::string> required(m_DeviceExtensions.begin(), m_DeviceExtensions.end());
|
||||
for (auto& ext : exts) {
|
||||
required.erase(ext.extensionName);
|
||||
}
|
||||
if (!required.empty()) return false;
|
||||
|
||||
auto formats = device.getSurfaceFormatsKHR(m_Surface);
|
||||
auto modes = device.getSurfacePresentModesKHR(m_Surface);
|
||||
if (formats.empty() || modes.empty()) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------- LogicalDevice ----------
|
||||
|
||||
void Device::CreateLogicalDevice()
|
||||
{
|
||||
auto families = m_PhysicalDevice.getQueueFamilyProperties();
|
||||
|
||||
for (uint32_t i = 0; i < families.size(); ++i) {
|
||||
if ((families[i].queueFlags & vk::QueueFlagBits::eGraphics) && m_GraphicsFamily == 0) {
|
||||
m_GraphicsFamily = i;
|
||||
}
|
||||
if (m_PhysicalDevice.getSurfaceSupportKHR(i, m_Surface) && m_PresentFamily == 0) {
|
||||
m_PresentFamily = i;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<vk::DeviceQueueCreateInfo> queueInfos;
|
||||
std::set<uint32_t> uniqueFamilies = { m_GraphicsFamily, m_PresentFamily };
|
||||
float priority = 1.0f;
|
||||
|
||||
for (uint32_t family : uniqueFamilies) {
|
||||
vk::DeviceQueueCreateInfo info;
|
||||
info.setQueueFamilyIndex(family)
|
||||
.setQueueCount(1)
|
||||
.setPQueuePriorities(&priority);
|
||||
queueInfos.push_back(info);
|
||||
}
|
||||
|
||||
vk::PhysicalDeviceFeatures features;
|
||||
|
||||
// ========================================================
|
||||
// 启用 1.4 特性链
|
||||
//
|
||||
// 同样的:1.3 的特性放 features13,1.4 的新特性放 features14。
|
||||
// 用 pNext 串联,最后挂到 DeviceCreateInfo 的 pNext 上。
|
||||
// ========================================================
|
||||
|
||||
vk::PhysicalDeviceVulkan14Features features14{};
|
||||
vk::PhysicalDeviceVulkan13Features features13{};
|
||||
|
||||
// 启用 1.3 的特性
|
||||
features13.dynamicRendering = VK_TRUE;
|
||||
features13.synchronization2 = VK_TRUE;
|
||||
|
||||
// 启用 1.4 的新特性
|
||||
features14.dynamicRenderingLocalRead = VK_TRUE;
|
||||
features14.pushDescriptor = VK_TRUE;
|
||||
|
||||
// 链接:1.4 → 1.3 → (shader object)
|
||||
features14.pNext = &features13;
|
||||
|
||||
// Shader Object 特性(如果支持)
|
||||
vk::PhysicalDeviceShaderObjectFeaturesEXT shaderObjFeatures{};
|
||||
if (m_ShaderObjectSupported) {
|
||||
shaderObjFeatures.shaderObject = VK_TRUE;
|
||||
features13.pNext = &shaderObjFeatures;
|
||||
}
|
||||
|
||||
// 扩展列表
|
||||
std::vector<const char*> deviceExtensions = m_DeviceExtensions;
|
||||
if (m_ShaderObjectSupported) {
|
||||
deviceExtensions.push_back(VK_EXT_SHADER_OBJECT_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
vk::DeviceCreateInfo createInfo;
|
||||
createInfo.setQueueCreateInfos(queueInfos)
|
||||
.setPEnabledExtensionNames(deviceExtensions)
|
||||
.setPEnabledFeatures(&features)
|
||||
.setPNext(&features14); // ← 特性链从这里开始
|
||||
|
||||
m_Device = m_PhysicalDevice.createDevice(createInfo);
|
||||
|
||||
m_GraphicsQueue = m_Device.getQueue(m_GraphicsFamily, 0);
|
||||
m_PresentQueue = m_Device.getQueue(m_PresentFamily, 0);
|
||||
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(m_Device);
|
||||
|
||||
ENGINE_INFO("Logical Device created (graphics={}, present={})",
|
||||
m_GraphicsFamily, m_PresentFamily);
|
||||
}
|
||||
|
||||
// ---------- 内存类型 ----------
|
||||
|
||||
uint32_t Device::FindMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags props) const
|
||||
{
|
||||
for (uint32_t i = 0; i < m_MemoryProperties.memoryTypeCount; ++i) {
|
||||
if ((typeFilter & (1 << i)) &&
|
||||
(m_MemoryProperties.memoryTypes[i].propertyFlags & props) == props) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
ENGINE_FATAL("No suitable memory type found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------- 验证层检查 ----------
|
||||
|
||||
bool Device::CheckValidationLayerSupport()
|
||||
{
|
||||
auto layers = vk::enumerateInstanceLayerProperties();
|
||||
for (const char* layerName : m_ValidationLayers) {
|
||||
bool found = false;
|
||||
for (auto& layer : layers) {
|
||||
if (std::strcmp(layer.layerName, layerName) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Engine
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/20.
|
||||
//
|
||||
|
||||
#include <Engine/Vulkan/MemoryAllocator.h>
|
||||
#include <Engine/Vulkan/Device.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Engine {
|
||||
|
||||
MemoryAllocator::MemoryAllocator(Device& device)
|
||||
: m_Device(device)
|
||||
{
|
||||
ENGINE_INFO("MemoryAllocator created");
|
||||
}
|
||||
|
||||
MemoryAllocator::~MemoryAllocator()
|
||||
{
|
||||
const auto& vkDevice = m_Device.GetDevice();
|
||||
|
||||
for (auto& blocks : m_Blocks) {
|
||||
for (auto& block : blocks) {
|
||||
if (block.IsMapped) {
|
||||
vkDevice.unmapMemory(block.Memory);
|
||||
}
|
||||
vkDevice.freeMemory(block.Memory);
|
||||
}
|
||||
blocks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
MemoryAllocator::Allocation MemoryAllocator::Allocate(
|
||||
vk::MemoryRequirements reqs, vk::MemoryPropertyFlags props)
|
||||
{
|
||||
uint32_t typeIndex = m_Device.FindMemoryType(reqs.memoryTypeBits, props);
|
||||
auto& blocks = m_Blocks[typeIndex];
|
||||
|
||||
// 先从已有块里找能放下的
|
||||
for (auto& block : blocks) {
|
||||
vk::DeviceSize alignedOffset =
|
||||
(block.Used + reqs.alignment - 1) & ~(reqs.alignment - 1);
|
||||
|
||||
if (alignedOffset + reqs.size <= block.Size) {
|
||||
Allocation alloc;
|
||||
alloc.Memory = block.Memory;
|
||||
alloc.Offset = alignedOffset;
|
||||
alloc.Size = reqs.size;
|
||||
alloc.MappedPtr = block.IsMapped
|
||||
? static_cast<char*>(block.MappedPtr) + alignedOffset
|
||||
: nullptr;
|
||||
|
||||
block.Used = alignedOffset + reqs.size;
|
||||
return alloc;
|
||||
}
|
||||
}
|
||||
|
||||
// 没有合适的块,创建新块
|
||||
vk::DeviceSize blockSize = std::max(m_DefaultBlockSize, reqs.size);
|
||||
|
||||
vk::MemoryAllocateInfo allocInfo;
|
||||
allocInfo.setAllocationSize(blockSize)
|
||||
.setMemoryTypeIndex(typeIndex);
|
||||
|
||||
Block block;
|
||||
block.Memory = m_Device.GetDevice().allocateMemory(allocInfo);
|
||||
block.Size = blockSize;
|
||||
block.Used = 0;
|
||||
|
||||
// HOST_VISIBLE 的块持久映射
|
||||
if (props & vk::MemoryPropertyFlagBits::eHostVisible) {
|
||||
block.MappedPtr = m_Device.GetDevice().mapMemory(
|
||||
block.Memory, 0, VK_WHOLE_SIZE);
|
||||
block.IsMapped = true;
|
||||
}
|
||||
|
||||
blocks.push_back(block);
|
||||
|
||||
// 从新块分配
|
||||
vk::DeviceSize alignedOffset = (reqs.alignment - 1) & ~(reqs.alignment - 1);
|
||||
|
||||
Allocation alloc;
|
||||
alloc.Memory = block.Memory;
|
||||
alloc.Offset = alignedOffset;
|
||||
alloc.Size = reqs.size;
|
||||
alloc.MappedPtr = block.IsMapped
|
||||
? static_cast<char*>(block.MappedPtr) + alignedOffset
|
||||
: nullptr;
|
||||
|
||||
blocks.back().Used = alignedOffset + reqs.size;
|
||||
|
||||
ENGINE_INFO("MemoryAllocator: new block of {} bytes (type {})",
|
||||
blockSize, typeIndex);
|
||||
|
||||
return alloc;
|
||||
}
|
||||
|
||||
} // namespace Engine
|
||||
@@ -0,0 +1,3 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/20.
|
||||
//
|
||||
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/20.
|
||||
//
|
||||
|
||||
|
||||
#include <Engine/Vulkan/ShaderManager.h>
|
||||
#include <Engine/Vulkan/Device.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace Engine {
|
||||
|
||||
ShaderManager::ShaderManager(Device& device)
|
||||
: m_Device(device)
|
||||
{
|
||||
}
|
||||
|
||||
ShaderManager::~ShaderManager()
|
||||
{
|
||||
}
|
||||
|
||||
ShaderObject ShaderManager::CreateFromFile(const std::string& spvPath,
|
||||
vk::ShaderStageFlagBits stage,
|
||||
vk::DescriptorSetLayout layout)
|
||||
{
|
||||
auto spirv = ReadSPIRV(spvPath);
|
||||
if (spirv.empty()) {
|
||||
ENGINE_ERROR("Failed to read SPIR-V: {}", spvPath);
|
||||
return { nullptr, stage };
|
||||
}
|
||||
return CreateFromSPIRV(spirv, stage, layout);
|
||||
}
|
||||
|
||||
ShaderObject ShaderManager::CreateFromSPIRV(const std::vector<u32>& spirv,
|
||||
vk::ShaderStageFlagBits stage,
|
||||
vk::DescriptorSetLayout layout)
|
||||
{
|
||||
// 下一个阶段:顶点 → 片段
|
||||
vk::ShaderStageFlags nextStage = {};
|
||||
if (stage == vk::ShaderStageFlagBits::eVertex) {
|
||||
nextStage = vk::ShaderStageFlagBits::eFragment;
|
||||
}
|
||||
|
||||
vk::ShaderCreateInfoEXT createInfo;
|
||||
createInfo.setStage(stage)
|
||||
.setNextStage(nextStage)
|
||||
.setCodeType(vk::ShaderCodeTypeEXT::eSpirv)
|
||||
.setCodeSize(spirv.size() * sizeof(u32))
|
||||
.setPCode(spirv.data())
|
||||
.setPName("main");
|
||||
|
||||
// 有布局就传进去
|
||||
if (layout) {
|
||||
createInfo.setSetLayouts(layout);
|
||||
}
|
||||
|
||||
vk::ShaderEXT shader;
|
||||
try {
|
||||
auto result = m_Device.GetDevice().createShadersEXT(createInfo);
|
||||
shader = result.value[0];
|
||||
}
|
||||
catch (const vk::SystemError& e) {
|
||||
ENGINE_ERROR("Failed to create shader object: {}", e.what());
|
||||
return { nullptr, stage };
|
||||
}
|
||||
|
||||
return { shader, stage };
|
||||
}
|
||||
|
||||
void ShaderManager::Destroy(const ShaderObject& shader)
|
||||
{
|
||||
if (shader.Handle) {
|
||||
m_Device.GetDevice().destroyShaderEXT(shader.Handle);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u32> ShaderManager::ReadSPIRV(const std::string& path)
|
||||
{
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
ENGINE_ERROR("Failed to open: {}", path);
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t size = file.tellg();
|
||||
std::vector<u32> buffer(size / sizeof(u32));
|
||||
file.seekg(0);
|
||||
file.read(reinterpret_cast<char*>(buffer.data()), size);
|
||||
file.close();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace Engine
|
||||
@@ -0,0 +1,255 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/20.
|
||||
//
|
||||
|
||||
#include <Engine/Vulkan/Swapchain.h>
|
||||
#include <Engine/Vulkan/Device.h>
|
||||
#include <Engine/Core/Window.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#include "Engine/Vulkan/MemoryAllocator.h"
|
||||
|
||||
namespace Engine
|
||||
{
|
||||
Swapchain::Swapchain(Device& device, MemoryAllocator& allocator, Window& window)
|
||||
: m_Device(device), m_Allocator(allocator)
|
||||
{
|
||||
Create(window);
|
||||
}
|
||||
|
||||
Swapchain::~Swapchain()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void Swapchain::Create(Window& window)
|
||||
{
|
||||
const auto& vkDevice = m_Device.GetDevice();
|
||||
auto surface = m_Device.GetSurface();
|
||||
auto physicalDevice = m_Device.GetPhysicalDevice();
|
||||
|
||||
// 查询支持
|
||||
auto caps = physicalDevice.getSurfaceCapabilitiesKHR(surface);
|
||||
auto formats = physicalDevice.getSurfaceFormatsKHR(surface);
|
||||
auto modes = physicalDevice.getSurfacePresentModesKHR(surface);
|
||||
|
||||
auto surfaceFormat = ChooseSurfaceFormat(formats);
|
||||
auto presentMode = ChoosePresentMode(modes);
|
||||
auto extent = ChooseExtent(caps, window);
|
||||
|
||||
m_Format = surfaceFormat.format;
|
||||
m_Extent = extent;
|
||||
|
||||
// 交换链 image 数量
|
||||
uint32_t imageCount = caps.minImageCount + 1;
|
||||
if (caps.maxImageCount > 0 && imageCount > caps.maxImageCount)
|
||||
{
|
||||
imageCount = caps.maxImageCount;
|
||||
}
|
||||
|
||||
vk::SwapchainCreateInfoKHR createInfo;
|
||||
createInfo.setSurface(surface)
|
||||
.setMinImageCount(imageCount)
|
||||
.setImageFormat(surfaceFormat.format)
|
||||
.setImageColorSpace(surfaceFormat.colorSpace)
|
||||
.setImageExtent(extent)
|
||||
.setImageArrayLayers(1)
|
||||
.setImageUsage(vk::ImageUsageFlagBits::eColorAttachment)
|
||||
.setImageSharingMode(vk::SharingMode::eExclusive)
|
||||
.setPreTransform(caps.currentTransform)
|
||||
.setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque)
|
||||
.setPresentMode(presentMode)
|
||||
.setClipped(VK_TRUE)
|
||||
.setOldSwapchain(nullptr);
|
||||
|
||||
// graphics 和 present 队列族不同时,用 concurrent
|
||||
uint32_t indices[] = {m_Device.GetGraphicsFamily(), m_Device.GetPresentFamily()};
|
||||
if (indices[0] != indices[1])
|
||||
{
|
||||
createInfo.setImageSharingMode(vk::SharingMode::eConcurrent)
|
||||
.setQueueFamilyIndices(indices);
|
||||
}
|
||||
|
||||
m_Swapchain = vkDevice.createSwapchainKHR(createInfo);
|
||||
|
||||
// 拿 image
|
||||
m_Images = vkDevice.getSwapchainImagesKHR(m_Swapchain);
|
||||
|
||||
// 创建 image view
|
||||
m_ImageViews.resize(m_Images.size());
|
||||
for (size_t i = 0; i < m_Images.size(); ++i)
|
||||
{
|
||||
vk::ImageViewCreateInfo viewInfo;
|
||||
viewInfo.setImage(m_Images[i])
|
||||
.setViewType(vk::ImageViewType::e2D)
|
||||
.setFormat(m_Format)
|
||||
.setSubresourceRange({
|
||||
vk::ImageAspectFlagBits::eColor,
|
||||
0, 1,
|
||||
0, 1
|
||||
});
|
||||
|
||||
m_ImageViews[i] = vkDevice.createImageView(viewInfo);
|
||||
}
|
||||
|
||||
ENGINE_INFO("Swapchain created: {}x{}, {} images, format={}",
|
||||
m_Extent.width, m_Extent.height,
|
||||
m_Images.size(),
|
||||
vk::to_string(m_Format));
|
||||
|
||||
CreateDepthResources();
|
||||
}
|
||||
|
||||
void Swapchain::Destroy()
|
||||
{
|
||||
DestroyDepthResources();
|
||||
|
||||
const auto& vkDevice = m_Device.GetDevice();
|
||||
|
||||
for (auto view : m_ImageViews)
|
||||
{
|
||||
vkDevice.destroyImageView(view);
|
||||
}
|
||||
m_ImageViews.clear();
|
||||
m_Images.clear();
|
||||
|
||||
if (m_Swapchain)
|
||||
{
|
||||
vkDevice.destroySwapchainKHR(m_Swapchain);
|
||||
m_Swapchain = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Swapchain::Recreate(Window& window)
|
||||
{
|
||||
const auto& vkDevice = m_Device.GetDevice();
|
||||
vkDevice.waitIdle();
|
||||
|
||||
Destroy();
|
||||
Create(window);
|
||||
|
||||
ENGINE_INFO("Swapchain recreated: {}x{}", m_Extent.width, m_Extent.height);
|
||||
}
|
||||
|
||||
// ---------- 选择函数 ----------
|
||||
|
||||
vk::SurfaceFormatKHR Swapchain::ChooseSurfaceFormat(const std::vector<vk::SurfaceFormatKHR>& formats)
|
||||
{
|
||||
for (const auto& f : formats)
|
||||
{
|
||||
if (f.format == vk::Format::eB8G8R8A8Srgb &&
|
||||
f.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear)
|
||||
{
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return formats[0];
|
||||
}
|
||||
|
||||
vk::PresentModeKHR Swapchain::ChoosePresentMode(const std::vector<vk::PresentModeKHR>& modes)
|
||||
{
|
||||
for (const auto& m : modes)
|
||||
{
|
||||
if (m == vk::PresentModeKHR::eMailbox)
|
||||
{
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return vk::PresentModeKHR::eFifo;
|
||||
}
|
||||
|
||||
vk::Extent2D Swapchain::ChooseExtent(const vk::SurfaceCapabilitiesKHR& caps, Window& window)
|
||||
{
|
||||
if (caps.currentExtent.width != std::numeric_limits<uint32_t>::max())
|
||||
{
|
||||
return caps.currentExtent;
|
||||
}
|
||||
|
||||
vk::Extent2D extent = {
|
||||
static_cast<uint32_t>(window.GetWidth()),
|
||||
static_cast<uint32_t>(window.GetHeight())
|
||||
};
|
||||
|
||||
extent.width = std::clamp(extent.width, caps.minImageExtent.width, caps.maxImageExtent.width);
|
||||
extent.height = std::clamp(extent.height, caps.minImageExtent.height, caps.maxImageExtent.height);
|
||||
|
||||
return extent;
|
||||
}
|
||||
|
||||
vk::Format Swapchain::FindDepthFormat()
|
||||
{
|
||||
// 优先 D32_SFLOAT,退回 D32_SFLOAT_S8_UINT,再退回 D24_UNORM_S8_UINT
|
||||
std::array<vk::Format, 3> candidates = {
|
||||
vk::Format::eD32Sfloat,
|
||||
vk::Format::eD32SfloatS8Uint,
|
||||
vk::Format::eD24UnormS8Uint
|
||||
};
|
||||
|
||||
for (auto format : candidates)
|
||||
{
|
||||
auto props = m_Device.GetPhysicalDevice().getFormatProperties(format);
|
||||
if (props.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment)
|
||||
{
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
||||
ENGINE_FATAL("No depth format supported");
|
||||
return vk::Format::eD32Sfloat;
|
||||
}
|
||||
|
||||
void Swapchain::CreateDepthResources()
|
||||
{
|
||||
m_DepthFormat = FindDepthFormat();
|
||||
|
||||
vk::ImageCreateInfo imageInfo;
|
||||
imageInfo.setImageType(vk::ImageType::e2D)
|
||||
.setFormat(m_DepthFormat)
|
||||
.setExtent({m_Extent.width, m_Extent.height, 1})
|
||||
.setMipLevels(1)
|
||||
.setArrayLayers(1)
|
||||
.setSamples(vk::SampleCountFlagBits::e1)
|
||||
.setTiling(vk::ImageTiling::eOptimal)
|
||||
.setUsage(vk::ImageUsageFlagBits::eDepthStencilAttachment)
|
||||
.setSharingMode(vk::SharingMode::eExclusive)
|
||||
.setInitialLayout(vk::ImageLayout::eUndefined);
|
||||
|
||||
m_DepthImage = m_Device.GetDevice().createImage(imageInfo);
|
||||
|
||||
auto reqs = m_Device.GetDevice().getImageMemoryRequirements(m_DepthImage);
|
||||
auto alloc = m_Allocator.Allocate(reqs, vk::MemoryPropertyFlagBits::eDeviceLocal);
|
||||
|
||||
m_DepthMemory = alloc.Memory;
|
||||
m_Device.GetDevice().bindImageMemory(m_DepthImage, m_DepthMemory, alloc.Offset);
|
||||
|
||||
vk::ImageViewCreateInfo viewInfo;
|
||||
viewInfo.setImage(m_DepthImage)
|
||||
.setViewType(vk::ImageViewType::e2D)
|
||||
.setFormat(m_DepthFormat)
|
||||
.setSubresourceRange({
|
||||
vk::ImageAspectFlagBits::eDepth,
|
||||
0, 1, 0, 1
|
||||
});
|
||||
|
||||
m_DepthImageView = m_Device.GetDevice().createImageView(viewInfo);
|
||||
}
|
||||
|
||||
void Swapchain::DestroyDepthResources()
|
||||
{
|
||||
const auto& vkDevice = m_Device.GetDevice();
|
||||
if (m_DepthImageView)
|
||||
{
|
||||
vkDevice.destroyImageView(m_DepthImageView);
|
||||
m_DepthImageView = nullptr;
|
||||
}
|
||||
if (m_DepthImage)
|
||||
{
|
||||
vkDevice.destroyImage(m_DepthImage);
|
||||
m_DepthImage = nullptr;
|
||||
}
|
||||
// 内存由 MemoryAllocator 统一释放
|
||||
}
|
||||
} // namespace Engine
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/20.
|
||||
//
|
||||
|
||||
|
||||
#include <Engine/Vulkan/SyncManager.h>
|
||||
#include <Engine/Vulkan/Device.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace Engine {
|
||||
|
||||
SyncManager::SyncManager(Device& device, u32 maxFramesInFlight, u32 imageCount)
|
||||
: m_Device(device)
|
||||
{
|
||||
vk::SemaphoreCreateInfo semInfo;
|
||||
|
||||
// 每帧一个 ImageAvailable
|
||||
m_ImageAvailable.resize(maxFramesInFlight);
|
||||
for (auto& sem : m_ImageAvailable) {
|
||||
sem = m_Device.GetDevice().createSemaphore(semInfo);
|
||||
}
|
||||
|
||||
// 每张交换链 image 一个 RenderFinished
|
||||
m_RenderFinished.resize(imageCount);
|
||||
for (auto& sem : m_RenderFinished) {
|
||||
sem = m_Device.GetDevice().createSemaphore(semInfo);
|
||||
}
|
||||
|
||||
// 每帧一个 Fence,初始 signaled
|
||||
vk::FenceCreateInfo fenceInfo;
|
||||
fenceInfo.setFlags(vk::FenceCreateFlagBits::eSignaled);
|
||||
m_FrameFences.resize(maxFramesInFlight);
|
||||
for (auto& fence : m_FrameFences) {
|
||||
fence = m_Device.GetDevice().createFence(fenceInfo);
|
||||
}
|
||||
|
||||
ENGINE_INFO("SyncManager created ({} frames, {} images)",
|
||||
maxFramesInFlight, imageCount);
|
||||
}
|
||||
|
||||
SyncManager::~SyncManager()
|
||||
{
|
||||
const auto& vkDevice = m_Device.GetDevice();
|
||||
for (auto f : m_FrameFences) vkDevice.destroyFence(f);
|
||||
for (auto s : m_ImageAvailable) vkDevice.destroySemaphore(s);
|
||||
for (auto s : m_RenderFinished) vkDevice.destroySemaphore(s);
|
||||
}
|
||||
|
||||
void SyncManager::WaitFence(u32 frameIndex)
|
||||
{
|
||||
auto result = m_Device.GetDevice().waitForFences(
|
||||
m_FrameFences[frameIndex], VK_TRUE, std::numeric_limits<u64>::max());
|
||||
if (result != vk::Result::eSuccess) {
|
||||
ENGINE_ERROR("waitForFences failed");
|
||||
}
|
||||
}
|
||||
|
||||
void SyncManager::ResetFence(u32 frameIndex)
|
||||
{
|
||||
m_Device.GetDevice().resetFences(m_FrameFences[frameIndex]);
|
||||
}
|
||||
} // namespace Engine
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/20.
|
||||
//
|
||||
|
||||
#include <Engine/Vulkan/UniformBuffer.h>
|
||||
#include <Engine/Vulkan/Device.h>
|
||||
#include <Engine/Vulkan/MemoryAllocator.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
|
||||
namespace Engine {
|
||||
|
||||
UniformBuffer::UniformBuffer(Device& device, MemoryAllocator& allocator, u64 size)
|
||||
: m_Size(size)
|
||||
{
|
||||
// Uniform Buffer 需要 CPU 每帧更新,所以用 HOST_VISIBLE | HOST_COHERENT
|
||||
m_Buffer = CreateScope<Buffer>(
|
||||
device, allocator,
|
||||
size,
|
||||
vk::BufferUsageFlagBits::eUniformBuffer,
|
||||
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||
vk::MemoryPropertyFlagBits::eHostCoherent);
|
||||
|
||||
ENGINE_INFO("UniformBuffer created: {} bytes", size);
|
||||
}
|
||||
|
||||
UniformBuffer::~UniformBuffer()
|
||||
{
|
||||
// Buffer 析构时自动销毁 vk::Buffer,内存由 MemoryAllocator 统一释放
|
||||
}
|
||||
|
||||
void UniformBuffer::Update(const void* data, u64 size)
|
||||
{
|
||||
m_Buffer->Upload(data, size);
|
||||
}
|
||||
|
||||
} // namespace Engine
|
||||
Reference in New Issue
Block a user