添加 Vulkan 实例和窗口管理功能,支持窗口创建与更新

This commit is contained in:
nanako
2026-01-04 16:49:33 +08:00
committed by nanako
parent 09b6481136
commit ad1b8a2e3d
20 changed files with 2683 additions and 2 deletions

View File

@@ -1,3 +1,17 @@
#include "app/mirai_app.h"
#include "core/object.h"
#include "render/vulkan_instance.h"
#include "window/window_manager.h"
using namespace mirai;
int main(int argc, char* argv[]) {
mirai_app_config config;
config.window_mgr_config.main_window.size = vec2i{800, 600};
mirai::mirai_app app;
app.setup(config);
app.run();
return 0;
}

View File

@@ -12,6 +12,11 @@ find_package(Stb REQUIRED)
simple_library(STATIC)
# 验证VulkanAPI版本
if (Vulkan_VERSION VERSION_LESS "1.3.0")
message(FATAL_ERROR "Vulkan version 1.3 or higher is required.")
endif()
target_link_libraries(${PROJECT_NAME} PUBLIC
Vulkan::Vulkan
SDL3::SDL3
@@ -23,3 +28,4 @@ target_link_libraries(${PROJECT_NAME} PUBLIC
fmt::fmt
)
target_link_directories(${PROJECT_NAME} PUBLIC ${Stb_INCLUDE_DIR})
target_compile_definitions(${PROJECT_NAME} PUBLIC VULKAN_HPP_NO_EXCEPTIONS VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1)

27
src/app/mirai_app.cpp Normal file
View File

@@ -0,0 +1,27 @@
#include "mirai_app.h"
#include "core/logger.h"
#include "core/time_util.h"
namespace mirai {
bool mirai_app::setup(const mirai_app_config& config) {
window_mgr = make_obj<window_manager>(config.window_mgr_config);
vulkan_instance_ = make_obj<vulkan_instance>(config.vulkan_instance_cfg);
return true;
}
int mirai_app::run() {
update_time();
while (!window_mgr->quit() && !g_quit_requested) {
auto delta_time = get_delta_time();
window_mgr->update(delta_time);
update_time();
}
shutdown();
return 0;
}
}

27
src/app/mirai_app.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <memory>
#include "render/vulkan_instance.h"
#include "window/window_manager.h"
namespace mirai {
struct mirai_app_config {
window_manager_config window_mgr_config{};
vulkan_instance_config vulkan_instance_cfg{};
};
class mirai_app {
public:
bool setup(const mirai_app_config& config);
void shutdown() {
vulkan_instance_.reset();
window_mgr.reset();
}
int run();
private:
std::shared_ptr<window_manager> window_mgr;
std::shared_ptr<vulkan_instance> vulkan_instance_;
};
}

View File

@@ -223,6 +223,9 @@ namespace mirai {
friend struct mirai::object_deleter; \
friend class object_registry;
template<typename T>
concept is_object_type = std::derived_from<typename T::parent_t, object>;
struct object_factory {
template<typename T, typename ...Args> requires std::derived_from<T, object>
static auto create(Args&&... args) {
@@ -232,7 +235,7 @@ namespace mirai {
}
};
template<typename T, typename ...Args> requires std::derived_from<T, object>
template<is_object_type T, typename ...Args>
auto make_obj(Args&&... args) {
return object_factory::create<T>(std::forward<Args>(args)...);
}

28
src/core/time_util.h Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
#include "types/types.h"
namespace mirai {
inline time_point last_time_point;
template<typename T = duration_ns>
auto epoch_time() {
return std::chrono::time_point_cast<T>(std::chrono::high_resolution_clock::now().time_since_epoch());
}
inline auto get_current_time() {
return std::chrono::high_resolution_clock::now();
}
inline auto update_time() {
auto now = get_current_time();
last_time_point = now;
return now;
}
inline auto get_delta_time() {
auto now = get_current_time();
auto delta = now - last_time_point;
last_time_point = now;
return delta;
}
}

1
src/render/allocator.cpp Normal file
View File

@@ -0,0 +1 @@
#include "allocator.h"

125
src/render/allocator.h Normal file
View File

@@ -0,0 +1,125 @@
#pragma once
#include "types/types.h"
#include "resource_types.h"
#include "core/object.h"
#include <vulkan/vulkan.hpp>
#include <vma/vk_mem_alloc.h>
namespace mirai {
struct allocation_info {
// 分配的大小(字节)
u64 size = 0;
// 偏移量(字节)
u64 offset = 0;
// 设备内存句柄
vk::DeviceMemory device_memory{};
// 映射的内存指针
void* mapped_ptr = nullptr;
// 内存类型索引
u32 memory_type_index = 0;
// 分配标志
vk::MemoryPropertyFlags property_flags{};
};
struct buffer_allocation_info {
/// Buffer 大小(字节)
u64 size = 0;
/// Buffer 用途
buffer_usage usage = buffer_usage::vertex;
/// 内存用途
memory_usage mem_usage = memory_usage::gpu_only;
/// 是否需要持久映射
bool persistent_mapped = false;
/// 调试名称
std::string_view debug_name;
};
/**
* @brief Buffer 分配结果
*/
struct buffer_allocation {
/// Vulkan Buffer 句柄
vk::Buffer buffer{};
/// VMA 分配句柄
VmaAllocation allocation = VK_NULL_HANDLE;
/// 分配信息
allocation_info info;
/// 是否有效
[[nodiscard]] bool is_valid() const noexcept {
return buffer && allocation != VK_NULL_HANDLE;
}
};
/**
* @brief Image 分配创建信息
*/
struct image_allocation_info {
/// 图像类型
vk::ImageType image_type = vk::ImageType::e2D;
/// 图像格式
vk::Format format = vk::Format::eR8G8B8A8Unorm;
/// 图像范围
vk::Extent3D extent = {1, 1, 1};
/// Mip 层级数
u32 mip_levels = 1;
/// 数组层数
u32 array_layers = 1;
/// 采样数
vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1;
/// 平铺方式
vk::ImageTiling tiling = vk::ImageTiling::eOptimal;
/// 图像用途
vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eSampled;
/// 初始布局
vk::ImageLayout initial_layout = vk::ImageLayout::eUndefined;
/// 内存用途
memory_usage mem_usage = memory_usage::gpu_only;
/// 是否为立方体贴图
bool is_cube = false;
/// 调试名称
std::string_view debug_name;
};
/**
* @brief Image 分配结果
*/
struct image_allocation {
/// Vulkan Image 句柄
vk::Image image{};
/// VMA 分配句柄
VmaAllocation allocation = VK_NULL_HANDLE;
/// 分配信息
allocation_info info;
/// 是否有效
[[nodiscard]] bool is_valid() const noexcept {
return image && allocation != VK_NULL_HANDLE;
}
};
/**
* @brief GPU 分配器配置
*/
struct allocator_config {
/// Vulkan 实例
vk::Instance instance{};
/// 物理设备
vk::PhysicalDevice physical_device{};
/// 逻辑设备
vk::Device device{};
/// Vulkan API 版本
u32 vulkan_api_version = VK_API_VERSION_1_3;
/// 是否启用 Buffer Device Address
bool enable_buffer_device_address = true;
/// 是否启用内存预算跟踪
bool enable_memory_budget = true;
/// 首选的大块分配大小(字节)
u64 preferred_large_heap_block_size = 256 * 1024 * 1024; // 256 MB
};
class vma_allocator : public object {
MIRAI_OBJECT_TYPE_INFO(vma_allocator, object)
explicit vma_allocator(const allocator_config& config);
~vma_allocator() override;
};
}

348
src/render/error.h Normal file
View File

@@ -0,0 +1,348 @@
#pragma once
#include "types/types.h"
#include <string>
#include <string_view>
#include <source_location>
#include <expected>
#include <fmt/format.h>
/**
* @brief 错误码枚举
*
* 定义了 MIRAI 框架中所有可能的错误类型
*/
enum class error_code : u32 {
/// 成功,无错误
success = 0,
// ---- 通用错误 (1-99) ----
/// 未知错误
unknown = 1,
/// 无效参数
invalid_argument = 2,
/// 空指针
null_pointer = 3,
/// 越界访问
out_of_bounds = 4,
/// 操作超时
timeout = 5,
/// 操作被取消
cancelled = 6,
/// 资源不可用
unavailable = 7,
/// 操作不支持
not_supported = 8,
/// 操作已完成
already_done = 9,
/// 状态无效
invalid_state = 10,
// ---- 内存错误 (100-199) ----
/// 内存分配失败
out_of_memory = 100,
/// 内存对齐错误
alignment_error = 101,
/// 内存泄漏检测
memory_leak = 102,
/// 双重释放
double_free = 103,
/// 使用已释放内存
use_after_free = 104,
// ---- 文件/IO 错误 (200-299) ----
/// 文件未找到
file_not_found = 200,
/// 文件访问被拒绝
access_denied = 201,
/// 文件已存在
file_exists = 202,
/// 文件读取错误
read_error = 203,
/// 文件写入错误
write_error = 204,
/// 文件格式错误
invalid_format = 205,
/// 文件损坏
corrupted_file = 206,
// ---- 图形/渲染错误 (300-399) ----
/// Vulkan 初始化失败
vulkan_init_failed = 300,
/// 设备不支持
device_not_supported = 301,
/// 着色器编译失败
shader_compilation_failed = 302,
/// 管线创建失败
pipeline_creation_failed = 303,
/// 交换链创建失败
swapchain_creation_failed = 304,
/// 帧缓冲创建失败
framebuffer_creation_failed = 305,
/// 命令缓冲错误
command_buffer_error = 306,
/// 同步错误
synchronization_error = 307,
/// 资源绑定错误
resource_binding_error = 308,
// ---- 窗口/输入错误 (400-499) ----
/// 窗口创建失败
window_creation_failed = 400,
/// 窗口已关闭
window_closed = 401,
/// 输入设备错误
input_device_error = 402,
/// 事件处理错误
event_error = 403,
// ---- 文本/字体错误 (500-599) ----
/// 字体加载失败
font_load_failed = 500,
/// 字形渲染失败
glyph_render_failed = 501,
/// 文本整形失败
text_shaping_failed = 502,
/// 编码转换失败
encoding_error = 503,
// ---- 资源错误 (600-699) ----
/// 资源未找到
resource_not_found = 600,
/// 资源加载失败
resource_load_failed = 601,
/// 资源已存在
resource_exists = 602,
/// 资源类型不匹配
resource_type_mismatch = 603,
// ---- 线程/并发错误 (700-799) ----
/// 线程创建失败
thread_creation_failed = 700,
/// 死锁检测
deadlock_detected = 701,
/// 竞态条件
race_condition = 702,
/// 任务执行失败
task_execution_failed = 703,
};
/**
* @brief 获取错误码的字符串描述
* @param code 错误码
* @return 错误描述字符串
*/
[[nodiscard]] constexpr std::string_view error_code_to_string(error_code code) noexcept {
switch (code) {
case error_code::success:
return "Success";
case error_code::unknown:
return "Unknown error";
case error_code::invalid_argument:
return "Invalid argument";
case error_code::null_pointer:
return "Null pointer";
case error_code::out_of_bounds:
return "Out of bounds";
case error_code::timeout:
return "Operation timeout";
case error_code::cancelled:
return "Operation cancelled";
case error_code::unavailable:
return "Resource unavailable";
case error_code::not_supported:
return "Operation not supported";
case error_code::already_done:
return "Operation already done";
case error_code::invalid_state:
return "Invalid state";
case error_code::out_of_memory:
return "Out of memory";
case error_code::alignment_error:
return "Memory alignment error";
case error_code::memory_leak:
return "Memory leak detected";
case error_code::double_free:
return "Double free detected";
case error_code::use_after_free:
return "Use after free detected";
case error_code::file_not_found:
return "File not found";
case error_code::access_denied:
return "Access denied";
case error_code::file_exists:
return "File already exists";
case error_code::read_error:
return "Read error";
case error_code::write_error:
return "Write error";
case error_code::invalid_format:
return "Invalid format";
case error_code::corrupted_file:
return "Corrupted file";
case error_code::vulkan_init_failed:
return "Vulkan initialization failed";
case error_code::device_not_supported:
return "Device not supported";
case error_code::shader_compilation_failed:
return "Shader compilation failed";
case error_code::pipeline_creation_failed:
return "Pipeline creation failed";
case error_code::swapchain_creation_failed:
return "Swapchain creation failed";
case error_code::framebuffer_creation_failed:
return "Framebuffer creation failed";
case error_code::command_buffer_error:
return "Command buffer error";
case error_code::synchronization_error:
return "Synchronization error";
case error_code::resource_binding_error:
return "Resource binding error";
case error_code::window_creation_failed:
return "Window creation failed";
case error_code::window_closed:
return "Window closed";
case error_code::input_device_error:
return "Input device error";
case error_code::event_error:
return "Event error";
case error_code::font_load_failed:
return "Font load failed";
case error_code::glyph_render_failed:
return "Glyph render failed";
case error_code::text_shaping_failed:
return "Text shaping failed";
case error_code::encoding_error:
return "Encoding error";
case error_code::resource_not_found:
return "Resource not found";
case error_code::resource_load_failed:
return "Resource load failed";
case error_code::resource_exists:
return "Resource already exists";
case error_code::resource_type_mismatch:
return "Resource type mismatch";
case error_code::thread_creation_failed:
return "Thread creation failed";
case error_code::deadlock_detected:
return "Deadlock detected";
case error_code::race_condition:
return "Race condition detected";
case error_code::task_execution_failed:
return "Task execution failed";
default:
return "Unknown error code";
}
}
/**
* @brief 错误信息结构
*
* 包含错误码和可选的详细错误消息
*/
struct error_info {
/// 错误码
error_code code{error_code::unknown};
/// 详细错误消息
std::string message{};
/// 源文件名
std::string_view file{};
/// 行号
u32 line{0};
/// 函数名
std::string_view function{};
/**
* @brief 默认构造函数
*/
constexpr error_info() noexcept = default;
/**
* @brief 从错误码构造
* @param ec 错误码
*/
constexpr explicit error_info(error_code ec) noexcept : code(ec), message(std::string(error_code_to_string(ec))) {
}
/**
* @brief 从错误码和消息构造
* @param ec 错误码
* @param msg 错误消息
*/
error_info(error_code ec, std::string msg) noexcept : code(ec), message(std::move(msg)) {
}
/**
* @brief 完整构造函数
* @param ec 错误码
* @param msg 错误消息
* @param f 源文件
* @param l 行号
* @param fn 函数名
*/
error_info(error_code ec, std::string msg,
std::string_view f, u32 l, std::string_view fn) noexcept : code(ec), message(std::move(msg)), file(f),
line(l), function(fn) {
}
/**
* @brief 检查是否为成功状态
* @return 如果错误码为 success 返回 true
*/
[[nodiscard]] constexpr bool is_success() const noexcept {
return code == error_code::success;
}
/**
* @brief 检查是否为错误状态
* @return 如果错误码不为 success 返回 true
*/
[[nodiscard]] constexpr bool is_error() const noexcept {
return code != error_code::success;
}
/**
* @brief 获取完整的错误描述
* @return 包含位置信息的完整错误描述
*/
[[nodiscard]] std::string full_description() const {
std::string desc = message;
if (!file.empty()) {
desc += " [at ";
desc += file;
desc += ":";
desc += std::to_string(line);
if (!function.empty()) {
desc += " in ";
desc += function;
}
desc += "]";
}
return desc;
}
};
template <typename T, typename E = error_info>
using result_t = std::expected<T, E>;
using void_result_t = result_t<void, error_info>;
#define MAKE_ERROR_INFO(ec, msg, ...) std::unexpected(error_info( \
ec, fmt::format(msg, __VA_ARGS__), \
std::source_location::current().file_name(), \
std::source_location::current().line(), \
std::source_location::current().function_name() \
))
#define MAKE_SUCCESS_INFO() error_info( \
error_code::success, \
"", \
std::source_location::current().file_name(), \
std::source_location::current().line(), \
std::source_location::current().function_name() \
)

538
src/render/resource_types.h Normal file
View File

@@ -0,0 +1,538 @@
#pragma once
#include <vulkan/vulkan.hpp>
#include "core/flag_enum.h"
#include "types/types.h"
namespace mirai {
enum class buffer_usage : u32 {
// 无用途
none = 0,
// 顶点缓冲区
vertex = 1 << 0,
// 索引缓冲区
index = 1 << 1,
// 统一缓冲区
uniform = 1 << 2,
// 存储缓冲区
storage = 1 << 3,
// 暂存缓冲区(用于数据传输)
staging = 1 << 4,
// 间接缓冲区
indirect = 1 << 5,
// 传输源缓冲区
transfer_src = 1 << 6,
// 传输目标缓冲区
transfer_dst = 1 << 7,
// 着色器设备地址缓冲区
shader_device_address = 1 << 8,
};
MIRAI_FLAG_ENUM(buffer_usage)
enum class memory_usage : u32 {
// GPU专用内存(不可映射)
gpu_only = 0,
// 仅CPU可见内存(可映射)
cpu_only = 1,
// CPU写入, GPU读取(上传)
cpu_to_gpu = 2,
// GPU写入, CPU读取(下载)
gpu_to_cpu = 3,
// 自动选择
auto_prefer_device = 4,
// 自动选择,优先主机内存
auto_prefer_host = 5,
};
/**
* @brief 纹理格式枚举
*
* 命名规则说明:
* - [components][bits]: 如 r8 (R通道8位), rgba32 (RGBA各32位)
* - [type]:
* - unorm: 无符号归一化整数 (Unsigned Normalized). 内存存整数,采样时映射到 [0.0, 1.0]
* - snorm: 有符号归一化整数 (Signed Normalized). 内存存整数,采样时映射到 [-1.0, 1.0]
* - uint: 无符号纯整数 (Unsigned Integer). 采样结果为 uint 类型 (不可线性过滤)
* - sint: 有符号纯整数 (Signed Integer). 采样结果为 int 类型 (不可线性过滤)
* - srgb: 非线性 sRGB 颜色空间. 读取时硬件自动执行 (val^2.2) 转换到线性空间
* - float: 浮点数. 支持超过 1.0 的值 (HDR)
*/
enum class texture_format : u32 {
/// @brief 未定义/无效格式
undefined = 0,
// =================================================================================
// ---- 8位格式 (每个分量8位) ----
// =================================================================================
/// @brief 单通道 8位 无符号归一化 [0, 1]
/// @usage 字体图集 (Font Atlas)、环境光遮蔽 (AO)、高度图、粗糙度/金属度贴图
r8_unorm,
/// @brief 单通道 8位 有符号归一化 [-1, 1]
r8_snorm,
/// @brief 单通道 8位 无符号整数 [0, 255]
/// @usage 索引纹理、物体ID标记 (Object ID)
r8_uint,
/// @brief 单通道 8位 有符号整数 [-128, 127]
r8_sint,
/// @brief 双通道 8位 无符号归一化
/// @usage 2D 距离场 (SDF)、简单的查找表 (LUT)
rg8_unorm,
/// @brief 双通道 8位 有符号归一化
/// @usage 简单的低精度法线贴图 (仅存XYZ重建)
rg8_snorm,
/// @brief 双通道 8位 无符号整数
rg8_uint,
/// @brief 双通道 8位 有符号整数
rg8_sint,
/// @brief 四通道 8位 无符号归一化 (红绿蓝透)
/// @usage 最通用的颜色纹理、漫反射贴图 (Diffuse/Albedo)、UI 纹理
rgba8_unorm,
/// @brief 四通道 8位 有符号归一化
rgba8_snorm,
/// @brief 四通道 8位 无符号整数
rgba8_uint,
/// @brief 四通道 8位 有符号整数
rgba8_sint,
/// @brief 四通道 8位 无符号归一化 (蓝绿红透)
/// @usage Windows 平台的 Swapchain (交换链) 默认首选格式,因为历史原因显存通常以 BGR 顺序存储
bgra8_unorm,
/// @brief 四通道 8位 sRGB 非线性空间
/// @usage 需要进行伽马校正的 Swapchain 呈现图像
bgra8_srgb,
// =================================================================================
// ---- 16位格式 (每个分量16位) ----
// =================================================================================
/// @brief 单通道 16位 无符号归一化 [0, 1]
/// @usage 高精度高度图 (地形渲染),避免梯田效应
r16_unorm,
/// @brief 单通道 16位 有符号归一化 [-1, 1]
r16_snorm,
/// @brief 单通道 16位 无符号整数
r16_uint,
/// @brief 单通道 16位 有符号整数
r16_sint,
/// @brief 单通道 16位 浮点数 (Half Float)
/// @usage 节省显存的 HDR 单通道数据
r16_float,
/// @brief 双通道 16位 无符号归一化
/// @usage 高精度 UV 坐标存储
rg16_unorm,
/// @brief 双通道 16位 有符号归一化
/// @usage 高品质法线贴图 (避免 8-bit 法线的波纹瑕疵)
rg16_snorm,
/// @brief 双通道 16位 无符号整数
rg16_uint,
/// @brief 双通道 16位 有符号整数
rg16_sint,
/// @brief 双通道 16位 浮点数
/// @usage 速度向量 (Velocity Buffer) 用于动态模糊或 TAA
rg16_float,
/// @brief 四通道 16位 无符号归一化
rgba16_unorm,
/// @brief 四通道 16位 有符号归一化
rgba16_snorm,
/// @brief 四通道 16位 无符号整数
rgba16_uint,
/// @brief 四通道 16位 有符号整数
rgba16_sint,
/// @brief 四通道 16位 浮点数 (Half Float)
/// @usage 标准的 HDR 场景渲染目标 (Scene Color),兼顾精度与带宽
rgba16_float,
// =================================================================================
// ---- 32位格式 (每个分量32位) ----
// =================================================================================
/// @brief 单通道 32位 无符号整数
/// @usage 原子计数器、大范围索引、GPGPU 数据
r32_uint,
/// @brief 单通道 32位 有符号整数
r32_sint,
/// @brief 单通道 32位 浮点数
/// @usage 深度纹理 (线性深度)、阴影贴图 (VSM)、科学计算数据
r32_float,
/// @brief 双通道 32位 无符号整数
rg32_uint,
/// @brief 双通道 32位 有符号整数
rg32_sint,
/// @brief 双通道 32位 浮点数
/// @usage 高精度复杂 UV 或 2D 坐标数据
rg32_float,
/// @brief 三通道 32位 无符号整数
/// @note 显卡对 3通道 (RGB) 格式支持较差,通常会有对齐填充,尽量使用 RGBA
rgb32_uint,
rgb32_sint,
rgb32_float,
/// @brief 四通道 32位 无符号整数
rgba32_uint,
/// @brief 四通道 32位 有符号整数
rgba32_sint,
/// @brief 四通道 32位 浮点数
/// @usage G-Buffer 中的世界坐标 (Position Buffer),或需要极高精度的物理模拟数据
rgba32_float,
// =================================================================================
// ---- 深度/模板格式 ----
// =================================================================================
/// @brief 16位 深度归一化 [0, 1]
/// @usage 节省显存的阴影贴图 (Shadow Map)
depth16_unorm,
/// @brief 24位 深度归一化 (通常打包在32位中)
depth24_unorm,
/// @brief 32位 浮点深度
/// @usage 现代桌面端游戏的标准深度缓冲格式 (Reverse Z 友好)
depth32_float,
/// @brief 24位 深度 + 8位 模板
/// @usage 经典的深度模板组合,适用于大多数需要模板测试的渲染
depth24_unorm_stencil8_uint,
/// @brief 32位 浮点深度 + 8位 模板
depth32_float_stencil8_uint,
/// @brief 仅 8位 模板
stencil8_uint,
// =================================================================================
// ---- 压缩格式 (Block Compression) ----
// BC 格式通过压缩 4x4 像素块来大幅减少显存占用 (通常减少 4-6 倍)
// =================================================================================
/// @brief BC1 (DXT1) RGB, 无 Alpha 或 1位 Alpha
/// @usage 不透明的颜色贴图 (Albedo),如石头、墙壁
bc1_rgb_unorm,
/// @brief BC1 (DXT1) sRGB
/// @usage 需要 sRGB 校正的不透明颜色贴图
bc1_rgb_srgb,
/// @brief BC1 (DXT1) RGBA
bc1_rgba_unorm,
/// @brief BC1 (DXT1) sRGBA
bc1_rgba_srgb,
/// @brief BC2 (DXT3) 显式 Alpha (4位清晰度)
/// @usage 具有急剧边缘 Alpha 的纹理 (如铁丝网),现代引擎较少使用
bc2_unorm,
/// @brief BC2 (DXT3) sRGB
bc2_srgb,
/// @brief BC3 (DXT5) 插值 Alpha
/// @usage 最常用的透明贴图格式 (如植被叶子、半透明玻璃)。Alpha 渐变平滑
bc3_unorm,
/// @brief BC3 (DXT5) sRGB
bc3_srgb,
/// @brief BC4 (ATI1) 单通道
/// @usage 压缩的高度图、高光强度图 (Gloss)、环境光遮蔽 (AO)
bc4_unorm,
/// @brief BC4 (ATI1) 单通道 有符号
bc4_snorm,
/// @brief BC5 (ATI2/3Dc) 双通道
/// @usage **法线贴图的标准压缩格式** (存储 X, Y在 Shader 中重建 Z)
bc5_unorm,
/// @brief BC5 双通道 有符号
bc5_snorm,
/// @brief BC6H 无符号浮点 (HDR)
/// @usage HDR 环境贴图 (Skybox)、光照探针 (Light Probe)。不带 Alpha。
bc6h_ufloat,
/// @brief BC6H 有符号浮点 (HDR)
bc6h_sfloat,
/// @brief BC7 高质量 RGBA
/// @usage 现代 GPU 的首选压缩格式。比 BC1/3 质量更好,伪影更少。适用于所有类型的颜色/Alpha 贴图。
bc7_unorm,
/// @brief BC7 高质量 sRGBA
bc7_srgb,
// =================================================================================
// ---- 特殊/组合格式 ----
// =================================================================================
/// @brief R11 G11 B10 浮点数 (无符号,无 Alpha)
/// @details 总共32位 (11+11+10)。只能存储正数。
/// @usage 极高效率的 HDR 颜色缓冲。常用于后期处理链 (Post-processing) 或无需 Alpha 的场景渲染。
/// 比 rgba16_float 节省一半显存带宽。
r11g11b10_float,
/// @brief RGB9 E5 共享指数浮点数
/// @details 3个通道共享 5位指数。
/// @usage 预计算的 HDR 数据,如天空盒或全局光照体积。不支持渲染目标 (Render Target) 输出。
rgb9e5_float,
/// @brief RGB 10位 + Alpha 2位 无符号归一化
/// @details 总共32位 (10+10+10+2)。
/// @usage G-Buffer 法线存储 (比 8位精度高比 16位省空间),或广色域 (HDR10) 输出。
rgb10a2_unorm,
/// @brief RGB 10位 + Alpha 2位 无符号整数
/// @usage 紧凑的数据打包 (如将 Mesh ID 和 Material ID 压入同一个 buffer)
rgb10a2_uint,
};
enum class texture_type : u32 {
texture_1d = 0,
texture_2d = 1,
texture_3d = 2,
texture_cube = 3,
texture_1d_array = 4,
texture_2d_array = 5,
texture_cube_array = 6,
};
enum class texture_usage : u32 {
// 采样器读取
sampled = 1 << 0,
// 存储图像
storage = 1 << 1,
// 渲染目标 (颜色附件)
color_attachment = 1 << 2,
// 深度/模板附件
depth_stencil_attachment = 1 << 3,
// 输入附件
input_attachment = 1 << 4,
// 传输源
transfer_src = 1 << 5,
// 传输目标
transfer_dst = 1 << 6,
// 瞬态附件
transient_attachment = 1 << 7,
};
MIRAI_FLAG_ENUM(texture_usage)
enum class sampler_filter : u32 {
nearest = 0,
linear = 1,
};
enum class sampler_address_mode : u32 {
repeat = 0,
mirrored_repeat = 1,
clamp_to_edge = 2,
clamp_to_border = 3,
mirror_clamp_to_edge = 4,
};
enum class sampler_mipmap_mode : u32 {
nearest = 0,
linear = 1,
};
enum class sampler_preset : u32 {
// 线性过滤,重复模式
linear_repeat = 0,
// 线性过滤,夹取模式
linear_clamp = 1,
// 最近点过滤,重复模式
nearest_repeat = 2,
// 最近点过滤,夹取模式
nearest_clamp = 3,
// 各向异性过滤,重复模式
anisotropic_repeat = 4,
// 各向异性过滤,夹取模式
anisotropic_clamp = 5,
// 阴影采样器 (比较采样器)
shadow_sampler = 6,
};
enum class border_color : u32 {
// 黑色透明(浮点数)
transparent_black_float = 0,
// 黑色透明(整数)
transparent_black_int = 1,
// 不透明黑色(浮点数)
opaque_black_float = 2,
// 不透明黑色(整数)
opaque_black_int = 3,
// 不透明白色(浮点数)
opaque_white_float = 4,
// 不透明白色(整数)
opaque_white_int = 5,
};
enum class compare_op : u32 {
never = 0,
less = 1,
equal = 2,
less_equal = 3,
greater = 4,
not_equal = 5,
greater_equal = 6,
always = 7,
};
enum class image_aspect : u32 {
color = 1 << 0,
depth = 1 << 1,
stencil = 1 << 2,
metadata = 1 << 3,
};
MIRAI_FLAG_ENUM(image_aspect)
enum class image_layout : u32 {
/// 未定义
undefined = 0,
/// 通用布局
general = 1,
/// 颜色附件最优
color_attachment_optimal = 2,
/// 深度模板附件最优
depth_stencil_attachment_optimal = 3,
/// 深度模板只读最优
depth_stencil_read_only_optimal = 4,
/// 着色器只读最优
shader_read_only_optimal = 5,
/// 传输源最优
transfer_src_optimal = 6,
/// 传输目标最优
transfer_dst_optimal = 7,
/// 预初始化
preinitialized = 8,
/// 深度只读模板附件最优
depth_read_only_stencil_attachment_optimal = 9,
/// 深度附件模板只读最优
depth_attachment_stencil_read_only_optimal = 10,
/// 深度附件最优
depth_attachment_optimal = 11,
/// 深度只读最优
depth_read_only_optimal = 12,
/// 模板附件最优
stencil_attachment_optimal = 13,
/// 模板只读最优
stencil_read_only_optimal = 14,
/// 只读最优
read_only_optimal = 15,
/// 附件最优
attachment_optimal = 16,
/// 呈现源
present_src = 17,
};
enum class sample_count : u32 {
count_1 = 1,
count_2 = 2,
count_4 = 4,
count_8 = 8,
count_16 = 16,
count_32 = 32,
count_64 = 64,
};
/**
* @brief 描述符类型
*/
enum class descriptor_type : u32 {
/// 采样器
sampler = 0,
/// 组合图像采样器
combined_image_sampler = 1,
/// 采样图像
sampled_image = 2,
/// 存储图像
storage_image = 3,
/// Uniform texel buffer
uniform_texel_buffer = 4,
/// Storage texel buffer
storage_texel_buffer = 5,
/// Uniform buffer
uniform_buffer = 6,
/// Storage buffer
storage_buffer = 7,
/// Uniform buffer dynamic
uniform_buffer_dynamic = 8,
/// Storage buffer dynamic
storage_buffer_dynamic = 9,
/// 输入附件
input_attachment = 10,
/// 加速结构
acceleration_structure = 11,
};
/**
* @brief 资源状态
*
* 用于跟踪资源的生命周期状态
*/
enum class resource_state : u32 {
/// 未初始化
uninitialized = 0,
/// 已创建但未上传数据
created = 1,
/// 正在上传数据
uploading = 2,
/// 就绪可用
ready = 3,
/// 正在销毁
destroying = 4,
/// 已销毁
destroyed = 5,
/// 错误状态
error = 6,
};
/**
* @brief 内存预算信息
*/
struct memory_budget {
/// 预算字节数(设备建议的最大使用量)
u64 budget_bytes = 0;
/// 当前使用字节数
u64 usage_bytes = 0;
/// 堆索引
u32 heap_index = 0;
/**
* @brief 获取剩余可用字节数
* @return 剩余字节数
*/
[[nodiscard]] constexpr u64 available_bytes() const noexcept {
return budget_bytes > usage_bytes ? budget_bytes - usage_bytes : 0;
}
/**
* @brief 获取使用百分比
* @return 使用百分比 (0.0 - 1.0)
*/
[[nodiscard]] constexpr f64 usage_percentage() const noexcept {
return budget_bytes > 0 ? static_cast<f64>(usage_bytes) / static_cast<f64>(budget_bytes) : 0.0;
}
};
/**
* @brief 分配器统计信息
*/
struct allocator_statistics {
/// 分配次数
u64 allocation_count = 0;
/// 总分配字节数
u64 total_allocated_bytes = 0;
/// 当前使用字节数
u64 used_bytes = 0;
/// 未使用字节数(碎片)
u64 unused_bytes = 0;
/// 块数量
u32 block_count = 0;
/// 堆预算数组
std::vector<memory_budget> heap_budgets;
/**
* @brief 获取碎片率
* @return 碎片率 (0.0 - 1.0)
*/
[[nodiscard]] f64 fragmentation_ratio() const noexcept {
u64 total = used_bytes + unused_bytes;
return total > 0 ? static_cast<f64>(unused_bytes) / static_cast<f64>(total) : 0.0;
}
};
}

View File

@@ -0,0 +1,780 @@
#pragma once
#include "resource_types.h"
namespace mirai {
[[nodiscard]] constexpr auto to_vulkan_buffer_usage(buffer_usage usage) noexcept {
vk::BufferUsageFlags2 flags{};
if (has_flag(usage, buffer_usage::vertex)) {
flags |= vk::BufferUsageFlagBits2::eVertexBuffer;
}
if (has_flag(usage, buffer_usage::index)) {
flags |= vk::BufferUsageFlagBits2::eIndexBuffer;
}
if (has_flag(usage, buffer_usage::uniform)) {
flags |= vk::BufferUsageFlagBits2::eUniformBuffer;
}
if (has_flag(usage, buffer_usage::storage)) {
flags |= vk::BufferUsageFlagBits2::eStorageBuffer;
}
if (has_flag(usage, buffer_usage::staging)) {
flags |= vk::BufferUsageFlagBits2::eTransferSrc | vk::BufferUsageFlagBits2::eTransferDst;
}
if (has_flag(usage, buffer_usage::indirect)) {
flags |= vk::BufferUsageFlagBits2::eIndirectBuffer;
}
if (has_flag(usage, buffer_usage::transfer_src)) {
flags |= vk::BufferUsageFlagBits2::eTransferSrc;
}
if (has_flag(usage, buffer_usage::transfer_dst)) {
flags |= vk::BufferUsageFlagBits2::eTransferDst;
}
if (has_flag(usage, buffer_usage::shader_device_address)) {
flags |= vk::BufferUsageFlagBits2::eShaderDeviceAddress;
}
return flags;
}
[[nodiscard]] constexpr auto to_vulkan_format(texture_format format) noexcept {
switch (format) {
case texture_format::undefined:
return vk::Format::eUndefined;
case texture_format::r8_unorm:
return vk::Format::eR8Unorm;
case texture_format::r8_snorm:
return vk::Format::eR8Snorm;
case texture_format::r8_uint:
return vk::Format::eR8Uint;
case texture_format::r8_sint:
return vk::Format::eR8Sint;
case texture_format::rg8_unorm:
return vk::Format::eR8G8Unorm;
case texture_format::rg8_snorm:
return vk::Format::eR8G8Snorm;
case texture_format::rg8_uint:
return vk::Format::eR8G8Uint;
case texture_format::rg8_sint:
return vk::Format::eR8G8Sint;
case texture_format::rgba8_unorm:
return vk::Format::eR8G8B8A8Unorm;
case texture_format::rgba8_snorm:
return vk::Format::eR8G8B8A8Snorm;
case texture_format::rgba8_uint:
return vk::Format::eR8G8B8A8Uint;
case texture_format::rgba8_sint:
return vk::Format::eR8G8B8A8Sint;
case texture_format::bgra8_unorm:
return vk::Format::eB8G8R8A8Unorm;
case texture_format::bgra8_srgb:
return vk::Format::eB8G8R8A8Srgb;
case texture_format::r16_unorm:
return vk::Format::eR16Unorm;
case texture_format::r16_snorm:
return vk::Format::eR16Snorm;
case texture_format::r16_uint:
return vk::Format::eR16Uint;
case texture_format::r16_sint:
return vk::Format::eR16Sint;
case texture_format::r16_float:
return vk::Format::eR16Sfloat;
case texture_format::rg16_unorm:
return vk::Format::eR16G16Unorm;
case texture_format::rg16_snorm:
return vk::Format::eR16G16Snorm;
case texture_format::rg16_uint:
return vk::Format::eR16G16Uint;
case texture_format::rg16_sint:
return vk::Format::eR16G16Sint;
case texture_format::rg16_float:
return vk::Format::eR16G16Sfloat;
case texture_format::rgba16_unorm:
return vk::Format::eR16G16B16A16Unorm;
case texture_format::rgba16_snorm:
return vk::Format::eR16G16B16A16Snorm;
case texture_format::rgba16_uint:
return vk::Format::eR16G16B16A16Uint;
case texture_format::rgba16_sint:
return vk::Format::eR16G16B16A16Sint;
case texture_format::rgba16_float:
return vk::Format::eR16G16B16A16Sfloat;
case texture_format::r32_uint:
return vk::Format::eR32Uint;
case texture_format::r32_sint:
return vk::Format::eR32Sint;
case texture_format::r32_float:
return vk::Format::eR32Sfloat;
case texture_format::rg32_uint:
return vk::Format::eR32G32Uint;
case texture_format::rg32_sint:
return vk::Format::eR32G32Sint;
case texture_format::rg32_float:
return vk::Format::eR32G32Sfloat;
case texture_format::rgb32_uint:
return vk::Format::eR32G32B32Uint;
case texture_format::rgb32_sint:
return vk::Format::eR32G32B32Sint;
case texture_format::rgb32_float:
return vk::Format::eR32G32B32Sfloat;
case texture_format::rgba32_uint:
return vk::Format::eR32G32B32A32Uint;
case texture_format::rgba32_sint:
return vk::Format::eR32G32B32A32Sint;
case texture_format::rgba32_float:
return vk::Format::eR32G32B32A32Sfloat;
case texture_format::depth16_unorm:
return vk::Format::eD16Unorm;
case texture_format::depth24_unorm:
return vk::Format::eX8D24UnormPack32;
case texture_format::depth32_float:
return vk::Format::eD32Sfloat;
case texture_format::depth24_unorm_stencil8_uint:
return vk::Format::eD24UnormS8Uint;
case texture_format::depth32_float_stencil8_uint:
return vk::Format::eD32SfloatS8Uint;
case texture_format::stencil8_uint:
return vk::Format::eS8Uint;
case texture_format::bc1_rgb_unorm:
return vk::Format::eBc1RgbUnormBlock;
case texture_format::bc1_rgb_srgb:
return vk::Format::eBc1RgbSrgbBlock;
case texture_format::bc1_rgba_unorm:
return vk::Format::eBc1RgbaUnormBlock;
case texture_format::bc1_rgba_srgb:
return vk::Format::eBc1RgbaSrgbBlock;
case texture_format::bc2_unorm:
return vk::Format::eBc2UnormBlock;
case texture_format::bc2_srgb:
return vk::Format::eBc2SrgbBlock;
case texture_format::bc3_unorm:
return vk::Format::eBc3UnormBlock;
case texture_format::bc3_srgb:
return vk::Format::eBc3SrgbBlock;
case texture_format::bc4_unorm:
return vk::Format::eBc4UnormBlock;
case texture_format::bc4_snorm:
return vk::Format::eBc4SnormBlock;
case texture_format::bc5_unorm:
return vk::Format::eBc5UnormBlock;
case texture_format::bc5_snorm:
return vk::Format::eBc5SnormBlock;
case texture_format::bc6h_ufloat:
return vk::Format::eBc6HUfloatBlock;
case texture_format::bc6h_sfloat:
return vk::Format::eBc6HSfloatBlock;
case texture_format::bc7_unorm:
return vk::Format::eBc7UnormBlock;
case texture_format::bc7_srgb:
return vk::Format::eBc7SrgbBlock;
case texture_format::r11g11b10_float:
return vk::Format::eB10G11R11UfloatPack32;
case texture_format::rgb9e5_float:
return vk::Format::eE5B9G9R9UfloatPack32;
case texture_format::rgb10a2_unorm:
return vk::Format::eA2R10G10B10UnormPack32;
case texture_format::rgb10a2_uint:
return vk::Format::eA2R10G10B10UintPack32;
default:
return vk::Format::eUndefined; // 未实现的格式返回未定义
}
}
[[nodiscard]] constexpr auto is_depth_format(texture_format format) {
switch (format) {
case texture_format::depth16_unorm:
case texture_format::depth24_unorm:
case texture_format::depth32_float:
case texture_format::depth24_unorm_stencil8_uint:
case texture_format::depth32_float_stencil8_uint:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr auto is_stencil_format(texture_format format) {
switch (format) {
case texture_format::depth24_unorm_stencil8_uint:
case texture_format::depth32_float_stencil8_uint:
case texture_format::stencil8_uint:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr auto is_depth_stencil_format(texture_format format) {
switch (format) {
case texture_format::depth24_unorm_stencil8_uint:
case texture_format::depth32_float_stencil8_uint:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr auto is_compressed_format(texture_format format) {
switch (format) {
case texture_format::bc1_rgb_unorm:
case texture_format::bc1_rgb_srgb:
case texture_format::bc1_rgba_unorm:
case texture_format::bc1_rgba_srgb:
case texture_format::bc2_unorm:
case texture_format::bc2_srgb:
case texture_format::bc3_unorm:
case texture_format::bc3_srgb:
case texture_format::bc4_unorm:
case texture_format::bc4_snorm:
case texture_format::bc5_unorm:
case texture_format::bc5_snorm:
case texture_format::bc6h_ufloat:
case texture_format::bc6h_sfloat:
case texture_format::bc7_unorm:
case texture_format::bc7_srgb:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr auto is_srgb_format(texture_format format) {
switch (format) {
case texture_format::bgra8_srgb:
case texture_format::bc1_rgb_srgb:
case texture_format::bc1_rgba_srgb:
case texture_format::bc2_srgb:
case texture_format::bc3_srgb:
case texture_format::bc7_srgb:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr u32 get_format_byte_size(texture_format format) {
switch (format) {
case texture_format::undefined:
return 0;
// ====================================================
// 1 Byte / Pixel (8 bits)
// ====================================================
case texture_format::r8_unorm:
case texture_format::r8_snorm:
case texture_format::r8_uint:
case texture_format::r8_sint:
case texture_format::stencil8_uint:
return 1;
// ====================================================
// 2 Bytes / Pixel (16 bits)
// ====================================================
case texture_format::rg8_unorm:
case texture_format::rg8_snorm:
case texture_format::rg8_uint:
case texture_format::rg8_sint:
// ----
case texture_format::r16_unorm:
case texture_format::r16_snorm:
case texture_format::r16_uint:
case texture_format::r16_sint:
case texture_format::r16_float:
// ----
case texture_format::depth16_unorm:
return 2;
// ====================================================
// 4 Bytes / Pixel (32 bits)
// ====================================================
case texture_format::rgba8_unorm:
case texture_format::rgba8_snorm:
case texture_format::rgba8_uint:
case texture_format::rgba8_sint:
case texture_format::bgra8_unorm:
case texture_format::bgra8_srgb:
// ----
case texture_format::rg16_unorm:
case texture_format::rg16_snorm:
case texture_format::rg16_uint:
case texture_format::rg16_sint:
case texture_format::rg16_float:
// ----
case texture_format::r32_uint:
case texture_format::r32_sint:
case texture_format::r32_float:
// ----
case texture_format::depth24_unorm: // 通常作为 X8_D24 pack32 存储
case texture_format::depth32_float:
case texture_format::depth24_unorm_stencil8_uint: // 24+8 = 32 bits
// ----
case texture_format::r11g11b10_float:
case texture_format::rgb9e5_float:
case texture_format::rgb10a2_unorm:
case texture_format::rgb10a2_uint:
return 4;
// ====================================================
// 8 Bytes / Pixel or Block (64 bits)
// ====================================================
// Uncompressed (Pixel size)
case texture_format::rgba16_unorm:
case texture_format::rgba16_snorm:
case texture_format::rgba16_uint:
case texture_format::rgba16_sint:
case texture_format::rgba16_float:
// ----
case texture_format::rg32_uint:
case texture_format::rg32_sint:
case texture_format::rg32_float:
// ----
// D32_S8 在 Vulkan 中通常需要 64位对齐 (4字节深度 + 4字节模板/填充)
case texture_format::depth32_float_stencil8_uint:
return 8;
// Compressed (Block size - 4x4 pixels)
// BC1 (DXT1) & BC4 (ATI1) use 64 bits per block
case texture_format::bc1_rgb_unorm:
case texture_format::bc1_rgb_srgb:
case texture_format::bc1_rgba_unorm:
case texture_format::bc1_rgba_srgb:
case texture_format::bc4_unorm:
case texture_format::bc4_snorm:
return 8;
// ====================================================
// 12 Bytes / Pixel (96 bits)
// ====================================================
case texture_format::rgb32_uint:
case texture_format::rgb32_sint:
case texture_format::rgb32_float:
return 12;
// ====================================================
// 16 Bytes / Pixel or Block (128 bits)
// ====================================================
// Uncompressed (Pixel size)
case texture_format::rgba32_uint:
case texture_format::rgba32_sint:
case texture_format::rgba32_float:
return 16;
// Compressed (Block size - 4x4 pixels)
// BC2, BC3, BC5, BC6H, BC7 use 128 bits per block
case texture_format::bc2_unorm:
case texture_format::bc2_srgb:
case texture_format::bc3_unorm:
case texture_format::bc3_srgb:
case texture_format::bc5_unorm:
case texture_format::bc5_snorm:
case texture_format::bc6h_ufloat:
case texture_format::bc6h_sfloat:
case texture_format::bc7_unorm:
case texture_format::bc7_srgb:
return 16;
default:
return 0;
}
}
[[nodiscard]] constexpr u32 get_format_channel_count(texture_format format) {
switch (format) {
case texture_format::undefined:
return 0;
// ====================================================
// 1 Channel (R, Depth, Stencil, Alpha-only, etc.)
// ====================================================
case texture_format::r8_unorm:
case texture_format::r8_snorm:
case texture_format::r8_uint:
case texture_format::r8_sint:
// ----
case texture_format::r16_unorm:
case texture_format::r16_snorm:
case texture_format::r16_uint:
case texture_format::r16_sint:
case texture_format::r16_float:
// ----
case texture_format::r32_uint:
case texture_format::r32_sint:
case texture_format::r32_float:
// ----
case texture_format::depth16_unorm:
case texture_format::depth24_unorm:
case texture_format::depth32_float:
case texture_format::stencil8_uint:
// ----
case texture_format::bc4_unorm:
case texture_format::bc4_snorm:
return 1;
// ====================================================
// 2 Channels (RG, Depth+Stencil)
// ====================================================
case texture_format::rg8_unorm:
case texture_format::rg8_snorm:
case texture_format::rg8_uint:
case texture_format::rg8_sint:
// ----
case texture_format::rg16_unorm:
case texture_format::rg16_snorm:
case texture_format::rg16_uint:
case texture_format::rg16_sint:
case texture_format::rg16_float:
// ----
case texture_format::rg32_uint:
case texture_format::rg32_sint:
case texture_format::rg32_float:
// ----
// Depth + Stencil 被视为 2 个独立的数据分量
case texture_format::depth24_unorm_stencil8_uint:
case texture_format::depth32_float_stencil8_uint:
// ----
case texture_format::bc5_unorm:
case texture_format::bc5_snorm:
return 2;
// ====================================================
// 3 Channels (RGB)
// ====================================================
case texture_format::rgb32_uint:
case texture_format::rgb32_sint:
case texture_format::rgb32_float:
// ----
case texture_format::bc1_rgb_unorm:
case texture_format::bc1_rgb_srgb:
// ----
// BC6H 仅支持 HDR RGB无 Alpha
case texture_format::bc6h_ufloat:
case texture_format::bc6h_sfloat:
// ----
case texture_format::r11g11b10_float:
case texture_format::rgb9e5_float:
return 3;
// ====================================================
// 4 Channels (RGBA, BGRA)
// ====================================================
case texture_format::rgba8_unorm:
case texture_format::rgba8_snorm:
case texture_format::rgba8_uint:
case texture_format::rgba8_sint:
case texture_format::bgra8_unorm:
case texture_format::bgra8_srgb:
// ----
case texture_format::rgba16_unorm:
case texture_format::rgba16_snorm:
case texture_format::rgba16_uint:
case texture_format::rgba16_sint:
case texture_format::rgba16_float:
// ----
case texture_format::rgba32_uint:
case texture_format::rgba32_sint:
case texture_format::rgba32_float:
// ----
case texture_format::bc1_rgba_unorm:
case texture_format::bc1_rgba_srgb:
case texture_format::bc2_unorm:
case texture_format::bc2_srgb:
case texture_format::bc3_unorm:
case texture_format::bc3_srgb:
case texture_format::bc7_unorm:
case texture_format::bc7_srgb:
// ----
case texture_format::rgb10a2_unorm:
case texture_format::rgb10a2_uint:
return 4;
default:
return 0;
}
}
[[nodiscard]] constexpr auto to_vulkan_image_type(texture_type type) noexcept {
switch (type) {
case texture_type::texture_1d:
case texture_type::texture_1d_array:
return vk::ImageType::e1D;
case texture_type::texture_2d:
case texture_type::texture_2d_array:
case texture_type::texture_cube:
case texture_type::texture_cube_array:
return vk::ImageType::e2D;
case texture_type::texture_3d:
return vk::ImageType::e3D;
default:
return vk::ImageType::e2D; // 默认返回 2D
}
}
[[nodiscard]] constexpr auto to_vulkan_image_view_type(texture_type type) noexcept {
switch (type) {
case texture_type::texture_1d:
return vk::ImageViewType::e1D;
case texture_type::texture_2d:
return vk::ImageViewType::e2D;
case texture_type::texture_3d:
return vk::ImageViewType::e3D;
case texture_type::texture_cube:
return vk::ImageViewType::eCube;
case texture_type::texture_1d_array:
return vk::ImageViewType::e1DArray;
case texture_type::texture_2d_array:
return vk::ImageViewType::e2DArray;
case texture_type::texture_cube_array:
return vk::ImageViewType::eCubeArray;
default:
return vk::ImageViewType::e2D; // 默认返回 2D
}
}
[[nodiscard]] constexpr auto to_vulkan_image_usage(texture_usage usage) noexcept {
vk::ImageUsageFlags flags;
if (usage & texture_usage::sampled) {
flags |= vk::ImageUsageFlagBits::eSampled;
}
if (usage & texture_usage::storage) {
flags |= vk::ImageUsageFlagBits::eStorage;
}
if (usage & texture_usage::color_attachment) {
flags |= vk::ImageUsageFlagBits::eColorAttachment;
}
if (usage & texture_usage::depth_stencil_attachment) {
flags |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
}
if (usage & texture_usage::input_attachment) {
flags |= vk::ImageUsageFlagBits::eInputAttachment;
}
if (usage & texture_usage::transfer_src) {
flags |= vk::ImageUsageFlagBits::eTransferSrc;
}
if (usage & texture_usage::transfer_dst) {
flags |= vk::ImageUsageFlagBits::eTransferDst;
}
if (usage & texture_usage::transient_attachment) {
flags |= vk::ImageUsageFlagBits::eTransientAttachment;
}
return flags;
}
[[nodiscard]] constexpr auto to_vulkan_filter(sampler_filter filter) noexcept {
switch (filter) {
case sampler_filter::nearest:
return vk::Filter::eNearest;
case sampler_filter::linear:
return vk::Filter::eLinear;
default:
return vk::Filter::eNearest; // 默认返回最近点过滤
}
}
[[nodiscard]] constexpr auto to_vulkan_address_mode(sampler_address_mode mode) noexcept {
switch (mode) {
case sampler_address_mode::repeat:
return vk::SamplerAddressMode::eRepeat;
case sampler_address_mode::mirrored_repeat:
return vk::SamplerAddressMode::eMirroredRepeat;
case sampler_address_mode::clamp_to_edge:
return vk::SamplerAddressMode::eClampToEdge;
case sampler_address_mode::clamp_to_border:
return vk::SamplerAddressMode::eClampToBorder;
case sampler_address_mode::mirror_clamp_to_edge:
return vk::SamplerAddressMode::eMirrorClampToEdge;
default:
return vk::SamplerAddressMode::eRepeat; // 默认返回重复模式
}
}
[[nodiscard]] constexpr auto to_vulkan_mipmap_mode(sampler_mipmap_mode mode) noexcept {
switch (mode) {
case sampler_mipmap_mode::nearest:
return vk::SamplerMipmapMode::eNearest;
case sampler_mipmap_mode::linear:
return vk::SamplerMipmapMode::eLinear;
default:
return vk::SamplerMipmapMode::eNearest; // 默认返回最近点过滤
}
}
[[nodiscard]] constexpr auto to_vulkan_border_color(border_color color) noexcept {
switch (color) {
case border_color::transparent_black_float:
return vk::BorderColor::eFloatTransparentBlack;
case border_color::transparent_black_int:
return vk::BorderColor::eIntTransparentBlack;
case border_color::opaque_black_float:
return vk::BorderColor::eFloatOpaqueBlack;
case border_color::opaque_black_int:
return vk::BorderColor::eIntOpaqueBlack;
case border_color::opaque_white_float:
return vk::BorderColor::eFloatOpaqueWhite;
case border_color::opaque_white_int:
return vk::BorderColor::eIntOpaqueWhite;
default:
return vk::BorderColor::eFloatTransparentBlack; // 默认返回透明黑色
}
}
[[nodiscard]] constexpr auto to_vulkan_compare_op(compare_op op) noexcept {
switch (op) {
case compare_op::never:
return vk::CompareOp::eNever;
case compare_op::less:
return vk::CompareOp::eLess;
case compare_op::equal:
return vk::CompareOp::eEqual;
case compare_op::less_equal:
return vk::CompareOp::eLessOrEqual;
case compare_op::greater:
return vk::CompareOp::eGreater;
case compare_op::not_equal:
return vk::CompareOp::eNotEqual;
case compare_op::greater_equal:
return vk::CompareOp::eGreaterOrEqual;
case compare_op::always:
return vk::CompareOp::eAlways;
default:
return vk::CompareOp::eAlways; // 默认返回总是通过
}
}
[[nodiscard]] constexpr auto to_vulkan_image_aspect(image_aspect aspect) noexcept {
switch (aspect) {
case image_aspect::color:
return vk::ImageAspectFlagBits::eColor;
case image_aspect::depth:
return vk::ImageAspectFlagBits::eDepth;
case image_aspect::stencil:
return vk::ImageAspectFlagBits::eStencil;
case image_aspect::metadata:
return vk::ImageAspectFlagBits::eMetadata;
default:
return vk::ImageAspectFlagBits::eColor; // 默认返回颜色方面
}
}
[[nodiscard]] constexpr auto get_default_aspect(texture_format format) noexcept {
if (is_depth_stencil_format(format)) {
return image_aspect::depth | image_aspect::stencil;
}
if (is_depth_format(format)) {
return image_aspect::depth;
}
if (is_stencil_format(format)) {
return image_aspect::stencil;
}
return image_aspect::color;
}
[[nodiscard]] constexpr auto to_vulkan_image_layout(image_layout layout) noexcept {
switch (layout) {
case image_layout::undefined:
return vk::ImageLayout::eUndefined;
case image_layout::general:
return vk::ImageLayout::eGeneral;
case image_layout::color_attachment_optimal:
return vk::ImageLayout::eColorAttachmentOptimal;
case image_layout::depth_stencil_attachment_optimal:
return vk::ImageLayout::eDepthStencilAttachmentOptimal;
case image_layout::depth_stencil_read_only_optimal:
return vk::ImageLayout::eDepthStencilReadOnlyOptimal;
case image_layout::shader_read_only_optimal:
return vk::ImageLayout::eShaderReadOnlyOptimal;
case image_layout::transfer_src_optimal:
return vk::ImageLayout::eTransferSrcOptimal;
case image_layout::transfer_dst_optimal:
return vk::ImageLayout::eTransferDstOptimal;
case image_layout::preinitialized:
return vk::ImageLayout::ePreinitialized;
case image_layout::depth_read_only_stencil_attachment_optimal:
return vk::ImageLayout::eDepthReadOnlyStencilAttachmentOptimal;
case image_layout::depth_attachment_stencil_read_only_optimal:
return vk::ImageLayout::eDepthAttachmentStencilReadOnlyOptimal;
case image_layout::depth_attachment_optimal:
return vk::ImageLayout::eDepthAttachmentOptimal;
case image_layout::depth_read_only_optimal:
return vk::ImageLayout::eDepthReadOnlyOptimal;
case image_layout::stencil_attachment_optimal:
return vk::ImageLayout::eStencilAttachmentOptimal;
case image_layout::stencil_read_only_optimal:
return vk::ImageLayout::eStencilReadOnlyOptimal;
case image_layout::read_only_optimal:
return vk::ImageLayout::eReadOnlyOptimal;
case image_layout::attachment_optimal:
return vk::ImageLayout::eAttachmentOptimal;
case image_layout::present_src:
return vk::ImageLayout::ePresentSrcKHR;
default:
return vk::ImageLayout::eUndefined;
}
}
[[nodiscard]] constexpr auto to_vulkan_sample_count(sample_count count) noexcept {
switch (count) {
case sample_count::count_1:
return vk::SampleCountFlagBits::e1;
case sample_count::count_2:
return vk::SampleCountFlagBits::e2;
case sample_count::count_4:
return vk::SampleCountFlagBits::e4;
case sample_count::count_8:
return vk::SampleCountFlagBits::e8;
case sample_count::count_16:
return vk::SampleCountFlagBits::e16;
case sample_count::count_32:
return vk::SampleCountFlagBits::e32;
case sample_count::count_64:
return vk::SampleCountFlagBits::e64;
default:
return vk::SampleCountFlagBits::e1; // 默认返回 1 样本
}
}
[[nodiscard]] constexpr auto to_vulkan_descriptor_type(descriptor_type type) noexcept {
switch (type) {
case descriptor_type::sampler:
return vk::DescriptorType::eSampler;
case descriptor_type::combined_image_sampler:
return vk::DescriptorType::eCombinedImageSampler;
case descriptor_type::sampled_image:
return vk::DescriptorType::eSampledImage;
case descriptor_type::storage_image:
return vk::DescriptorType::eStorageImage;
case descriptor_type::uniform_texel_buffer:
return vk::DescriptorType::eUniformTexelBuffer;
case descriptor_type::storage_texel_buffer:
return vk::DescriptorType::eStorageTexelBuffer;
case descriptor_type::uniform_buffer:
return vk::DescriptorType::eUniformBuffer;
case descriptor_type::storage_buffer:
return vk::DescriptorType::eStorageBuffer;
case descriptor_type::uniform_buffer_dynamic:
return vk::DescriptorType::eUniformBufferDynamic;
case descriptor_type::storage_buffer_dynamic:
return vk::DescriptorType::eStorageBufferDynamic;
case descriptor_type::input_attachment:
return vk::DescriptorType::eInputAttachment;
case descriptor_type::acceleration_structure:
return vk::DescriptorType::eAccelerationStructureKHR;
default:
return vk::DescriptorType::eSampler;
}
}
}

View File

@@ -0,0 +1,359 @@
#include "vulkan_instance.h"
#include "vulkan_types.h"
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
#include <set>
#include <SDL3/SDL.h>
#include <SDL3/SDL_vulkan.h>
#include "core/logger.h"
namespace mirai {
#if MIRAI_DEBUG
/// Vulkan 验证层名称
constexpr const char* validation_layer_name = "VK_LAYER_KHRONOS_validation";
/// 验证层列表
constexpr std::array<const char*, 1> validation_layers = {
validation_layer_name
};
#endif
/// 必需的设备扩展
constexpr std::array<const char*, 1> required_device_extensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
/// 可选的设备扩展(用于额外功能)
constexpr std::array<const char*, 3> optional_device_extensions = {
VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME,
VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME,
VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME
};
vulkan_instance::vulkan_instance(const vulkan_instance_config& config) {
config_ = config;
}
bool vulkan_instance::is_extension_supported(const char* extension_name) {
const auto& extensions = get_available_extensions();
return std::ranges::any_of(extensions, [extension_name](const vk::ExtensionProperties& ext) {
return std::strcmp(ext.extensionName, extension_name) == 0;
});
}
bool vulkan_instance::is_layer_supported(const char* layer_name) {
const auto& layers = get_available_layers();
return std::ranges::any_of(layers, [layer_name](const vk::LayerProperties& layer) {
return std::strcmp(layer.layerName, layer_name) == 0;
});
}
std::vector<vk::ExtensionProperties> vulkan_instance::get_available_extensions() {
auto [result, extensions] = vk::enumerateInstanceExtensionProperties();
if (result != vk::Result::eSuccess) {
MIRAI_LOG_ERROR("Failed to enumerate Vulkan instance extensions: {}", vk::to_string(result));
return {};
}
return extensions;
}
std::vector<vk::LayerProperties> vulkan_instance::get_available_layers() {
auto [result, layers] = vk::enumerateInstanceLayerProperties();
if (result != vk::Result::eSuccess) {
MIRAI_LOG_ERROR("Failed to enumerate Vulkan instance layers: {}", vk::to_string(result));
return {};
}
return layers;
}
u32 vulkan_instance::get_supported_api_version() {
auto [result, version] = vk::enumerateInstanceVersion();
if (result != vk::Result::eSuccess) {
MIRAI_LOG_ERROR("Failed to get supported Vulkan API version: {}", vk::to_string(result));
return VK_API_VERSION_1_0;
}
return version;
}
void vulkan_instance::on_created() {
object::on_created();
{
const vk_dynamic_loader dl;
auto proc_addr = dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr");
VULKAN_HPP_DEFAULT_DISPATCHER.init(proc_addr);
}
auto result = create_instance(config_);
if (!result.has_value()) {
MIRAI_LOG_ERROR("Failed to create Vulkan instance: {}", result.error().full_description());
return;
}
#if MIRAI_DEBUG
if (validation_enabled_) {
result = setup_debug_messenger();
if (!result.has_value()) {
MIRAI_LOG_WARN("Failed to setup debug messenger: {}", result.error().full_description());
}
}
#endif
MIRAI_LOG_INFO("Vulkan instance object created");
}
void vulkan_instance::on_destroying() {
object::on_destroying();
MIRAI_LOG_INFO("Vulkan instance object destroying");
#if MIRAI_DEBUG
if (debug_messenger_) {
destroy_debug_messenger();
}
#endif
if (instance_) {
instance_.destroy();
instance_ = nullptr;
}
SDL_Quit();
}
void_result_t vulkan_instance::create_instance(const vulkan_instance_config& config) {
#if MIRAI_DEBUG
validation_enabled_ = config.enable_validation;
if (validation_enabled_ && !is_layer_supported(validation_layer_name)) {
MIRAI_LOG_WARN("Vulkan validation layer not supported, disabling validation");
validation_enabled_ = false;
}
#endif
api_version_ = config.api_version;
u32 supported_version = get_supported_api_version();
if (supported_version < api_version_) {
return MAKE_ERROR_INFO(error_code::vulkan_init_failed,
"Requested Vulkan API version not supported");
}
// 应用信息
vk::ApplicationInfo app_info{};
app_info.setPApplicationName(config.app_name.c_str())
.setApplicationVersion(config.app_version)
.setPEngineName(config.engine_name.c_str())
.setEngineVersion(config.engine_version)
.setApiVersion(api_version_);
// 获取必需的扩展
enabled_extensions_ = get_required_extensions(config);
// 验证所有扩展都可用
for (const char* ext : enabled_extensions_) {
if (!is_extension_supported(ext)) {
return MAKE_ERROR_INFO(error_code::vulkan_init_failed,
"Required extension not supported: {}", ext);
}
}
// 设置层
enabled_layers_.clear();
if (validation_enabled_) {
enabled_layers_.push_back(validation_layer_name);
}
for (const char* layer : config.extra_layers) {
if (is_layer_supported(layer)) {
enabled_layers_.push_back(layer);
}
}
// 实例创建信息
vk::InstanceCreateInfo create_info{};
create_info.setPApplicationInfo(&app_info)
.setEnabledExtensionCount(static_cast<u32>(enabled_extensions_.size()))
.setPpEnabledExtensionNames(enabled_extensions_.data())
.setEnabledLayerCount(static_cast<u32>(enabled_layers_.size()))
.setPpEnabledLayerNames(enabled_layers_.data());
#if MIRAI_DEBUG
// 调试信使创建信息(用于实例创建/销毁阶段的调试)
VkDebugUtilsMessengerCreateInfoEXT debug_create_info{};
debug_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
if (validation_enabled_) {
debug_create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
debug_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
debug_create_info.pfnUserCallback = debug_callback;
debug_create_info.pUserData = this;
create_info.setPNext(&debug_create_info);
}
#endif
// 创建实例
auto [result, instance] = vk::createInstance(create_info);
if (result != vk::Result::eSuccess) {
return MAKE_ERROR_INFO(error_code::vulkan_init_failed,
"Failed to create Vulkan instance: {}", vk::to_string(result));
}
VULKAN_HPP_DEFAULT_DISPATCHER.init(instance);
instance_ = instance;
MIRAI_LOG_INFO("Vulkan instance created with API version {}.{}.{}",
VK_VERSION_MAJOR(api_version_),
VK_VERSION_MINOR(api_version_),
VK_VERSION_PATCH(api_version_));
return {};
}
auto vulkan_instance::get_required_extensions(
const vulkan_instance_config& config) const -> std::vector<const char*> {
std::vector<const char*> extensions;
// 获取 SDL 需要的扩展
u32 sdl_extension_count = 0;
const char* const* sdl_extensions = SDL_Vulkan_GetInstanceExtensions(&sdl_extension_count);
if (sdl_extensions != nullptr) {
for (u32 i = 0; i < sdl_extension_count; ++i) {
extensions.push_back(sdl_extensions[i]);
}
}
#if MIRAI_DEBUG
// 如果启用验证层,添加调试扩展
if (config.enable_validation) {
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
#endif
// 添加额外的扩展
for (const char* ext : config.extra_extensions) {
extensions.push_back(ext);
}
// 去重
std::set<std::string_view> unique_extensions;
std::vector<const char*> result;
for (const char* ext : extensions) {
if (unique_extensions.insert(ext).second) {
result.push_back(ext);
}
}
return result;
}
#if MIRAI_DEBUG
VkBool32 vulkan_instance::debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_type,
const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
void* user_data) {
auto* instance = static_cast<vulkan_instance*>(user_data);
// 转换严重级别
debug_severity severity;
if (message_severity & (VkDebugUtilsMessageSeverityFlagBitsEXT)
vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose) {
severity = debug_severity::verbose;
}
else if (message_severity & (VkDebugUtilsMessageSeverityFlagBitsEXT)
vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo) {
severity = debug_severity::info;
}
else if (message_severity & (VkDebugUtilsMessageSeverityFlagBitsEXT)
vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) {
severity = debug_severity::warning;
}
else {
severity = debug_severity::error;
}
// 转换消息类型
debug_type type;
if (message_type & (VkDebugUtilsMessageTypeFlagsEXT)vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation) {
type = debug_type::validation;
}
else if (message_type & (VkDebugUtilsMessageTypeFlagsEXT)vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance) {
type = debug_type::performance;
}
else {
type = debug_type::general;
}
// 调用用户回调
if (instance != nullptr && instance->user_debug_callback_) {
instance->user_debug_callback_(severity, type, callback_data->pMessage);
}
// 使用日志系统记录
switch (severity) {
case debug_severity::verbose:
MIRAI_LOG_TRACE("[Vulkan] {}", callback_data->pMessage);
break;
case debug_severity::info:
MIRAI_LOG_DEBUG("[Vulkan] {}", callback_data->pMessage);
break;
case debug_severity::warning:
MIRAI_LOG_WARN("[Vulkan] {}", callback_data->pMessage);
break;
case debug_severity::error:
MIRAI_LOG_ERROR("[Vulkan] {}", callback_data->pMessage);
break;
}
return VK_FALSE;
}
void_result_t vulkan_instance::setup_debug_messenger() {
if (!validation_enabled_ || !instance_) {
return {};
}
// 使用 C API 创建调试信使
auto func = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(
vkGetInstanceProcAddr(instance_, "vkCreateDebugUtilsMessengerEXT"));
if (!func) {
return MAKE_ERROR_INFO(error_code::vulkan_init_failed, "Could not load vkCreateDebugUtilsMessengerEXT");
}
VkDebugUtilsMessengerCreateInfoEXT create_info{};
create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
create_info.pfnUserCallback = debug_callback;
create_info.pUserData = this;
VkDebugUtilsMessengerEXT messenger = VK_NULL_HANDLE;
auto result = static_cast<vk::Result>(func(static_cast<VkInstance>(instance_), &create_info, nullptr,
&messenger));
if (result != vk::Result::eSuccess) {
return MAKE_ERROR_INFO(error_code::vulkan_init_failed, "Failed to create debug messenger: {}", vk::to_string(result));
}
debug_messenger_ = messenger;
MIRAI_LOG_DEBUG("Vulkan debug messenger created");
return {};
}
void vulkan_instance::destroy_debug_messenger() {
if (!debug_messenger_ || !instance_) {
return;
}
auto func = reinterpret_cast<PFN_vkDestroyDebugUtilsMessengerEXT>(
vkGetInstanceProcAddr(instance_, "vkDestroyDebugUtilsMessengerEXT"));
if (func) {
func(static_cast<VkInstance>(instance_), static_cast<VkDebugUtilsMessengerEXT>(debug_messenger_), nullptr);
}
debug_messenger_ = nullptr;
}
#endif
}

View File

@@ -0,0 +1,158 @@
#pragma once
#include "core/object.h"
#include "error.h"
#include <vulkan/vulkan.hpp>
#include <functional>
namespace mirai {
/**
* @brief 调试消息严重级别
*/
enum class debug_severity : u8 {
verbose = 0,
info = 1,
warning = 2,
error = 3
};
/**
* @brief 调试消息类型
*/
enum class debug_type : u8 {
general = 0,
validation = 1,
performance = 2
};
/**
* @brief 调试消息回调函数类型
* @param severity 严重级别
* @param type 消息类型
* @param message 消息内容
*/
using debug_callback_fn = std::function<void(debug_severity severity,
debug_type type,
std::string_view message)>;
/**
* @brief Vulkan 实例配置
*/
struct vulkan_instance_config {
/// 应用程序名称
std::string app_name = "MIRAI Application";
/// 应用程序版本
u32 app_version = VK_MAKE_VERSION(1, 0, 0);
/// 引擎名称
std::string engine_name = "MIRAI Engine";
/// 引擎版本
u32 engine_version = VK_MAKE_VERSION(0, 1, 0);
/// 要求的 Vulkan API 版本
u32 api_version = VK_API_VERSION_1_3;
/// 是否启用验证层
bool enable_validation = MIRAI_DEBUG;
/// 额外的实例扩展
std::vector<const char*> extra_extensions;
/// 额外的验证层
std::vector<const char*> extra_layers;
};
class vulkan_instance : public object {
MIRAI_OBJECT_TYPE_INFO(vulkan_instance, object)
explicit vulkan_instance(const vulkan_instance_config& config = {});
// ============================================================================================
// 扩展和层查询
// ============================================================================================
/**
* @brief 获取已启用的实例扩展列表
* @return 扩展名称列表
*/
[[nodiscard]] const std::vector<const char*>& get_enabled_extensions() const noexcept {
return enabled_extensions_;
}
/**
* @brief 获取已启用的验证层列表
* @return 层名称列表
*/
[[nodiscard]] const std::vector<const char*>& get_enabled_layers() const noexcept {
return enabled_layers_;
}
/**
* @brief 检查是否支持指定的实例扩展
* @param extension_name 扩展名称
* @return 如果支持返回 true
*/
[[nodiscard]] static bool is_extension_supported(const char* extension_name);
/**
* @brief 检查是否支持指定的验证层
* @param layer_name 层名称
* @return 如果支持返回 true
*/
[[nodiscard]] static bool is_layer_supported(const char* layer_name);
/**
* @brief 获取所有可用的实例扩展
* @return 扩展属性列表
*/
[[nodiscard]] static std::vector<vk::ExtensionProperties> get_available_extensions();
/**
* @brief 获取所有可用的验证层
* @return 层属性列表
*/
[[nodiscard]] static std::vector<vk::LayerProperties> get_available_layers();
/**
* @brief 获取要求的 API 版本
* @return API 版本
*/
[[nodiscard]] u32 get_api_version() const noexcept {
return api_version_;
}
/**
* @brief 获取实例支持的最高 API 版本
* @return 支持的 API 版本
*/
[[nodiscard]] static u32 get_supported_api_version();
protected:
void on_created() override;
void on_destroying() override;
private:
void_result_t create_instance(const vulkan_instance_config& config);
[[nodiscard]] auto get_required_extensions(const vulkan_instance_config& config) const -> std::vector<const char*>;
vk::Instance instance_;
bool validation_enabled_{MIRAI_DEBUG};
u32 api_version_{VK_API_VERSION_1_3};
std::vector<const char*> enabled_extensions_;
std::vector<const char*> enabled_layers_;
vulkan_instance_config config_;
#if MIRAI_DEBUG
static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_type,
const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
void* user_data);
void_result_t setup_debug_messenger();
void destroy_debug_messenger();
vk::DebugUtilsMessengerEXT debug_messenger_;
debug_callback_fn user_debug_callback_;
#endif
};
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include <vulkan/vulkan.hpp>
#if VK_HEADER_VERSION >= 304
using vk_dynamic_loader = vk::detail::DynamicLoader;
#else
using vk_dynamic_loader = vk::DynamicLoader;
#endif

View File

@@ -3,6 +3,9 @@
#include <cstdint>
#include <cwchar>
#include <span>
#include <chrono>
#include <Eigen/Eigen>
using u8 = std::uint8_t;
using u16 = std::uint16_t;
@@ -23,3 +26,22 @@ using intptr_type = std::intptr_t;
using byte_type = std::byte;
using byte_span = std::span<byte_type>;
using const_byte_span = std::span<const byte_type>;
using duration_ns = std::chrono::duration<f64, std::nano>;
using duration_ms = std::chrono::duration<f64, std::milli>;
using duration_s = std::chrono::duration<f64, std::ratio<1>>;
using time_point = std::chrono::steady_clock::time_point;
using vec2 = Eigen::Vector2f;
using vec3 = Eigen::Vector3f;
using vec4 = Eigen::Vector4f;
using vec2i = Eigen::Vector2i;
using vec3i = Eigen::Vector3i;
using vec4i = Eigen::Vector4i;
using mat3 = Eigen::Matrix3f;
using mat4 = Eigen::Matrix4f;
using quat = Eigen::Quaternionf;
using transform = Eigen::Transform<f32, 3, Eigen::Affine>;
using rect2d = Eigen::AlignedBox2f;
inline bool g_quit_requested = false;

61
src/window/window.cpp Normal file
View File

@@ -0,0 +1,61 @@
#include "window.h"
#include "core/logger.h"
namespace mirai {
void_result_t window::make_window(window_config config) {
if (config.vulkan_window) {
config.flags |= SDL_WINDOW_VULKAN;
}
else {
config.flags &= ~SDL_WINDOW_VULKAN;
}
auto win_ptr = SDL_CreateWindow(config.title.c_str(), config.size.x(), config.size.y(), config.flags);
if (!win_ptr) {
return MAKE_ERROR_INFO(error_code::window_creation_failed,
"SDL_CreateWindow 失败: {}", SDL_GetError());
}
window_ = win_ptr;
return {};
}
void window::update(duration_ms delta_time) {
}
void window::show_window(bool show) {
if (show) {
SDL_ShowWindow(window_);
}
else {
SDL_HideWindow(window_);
}
}
void window::move_window(vec2i pos) {
SDL_SetWindowPosition(window_, pos.x(), pos.y());
}
vec2i window::get_pos() const {
int x, y;
if (!SDL_GetWindowPosition(window_, &x, &y)) {
MIRAI_LOG_ERROR("SDL_GetWindowPosition failed: %s", SDL_GetError());
x = 0;
y = 0;
}
return vec2i{x, y};
}
void window::on_created() {
object::on_created();
}
void window::on_destroying() {
object::on_destroying();
SDL_DestroyWindow(window_);
window_ = nullptr;
}
}

44
src/window/window.h Normal file
View File

@@ -0,0 +1,44 @@
#pragma once
#include "core/object.h"
#include <SDL3/SDL.h>
#include "render/error.h"
namespace mirai {
struct window_config {
vec2i size;
// 窗口位置, 空表示默认位置SDL_WINDOWPOS_CENTERED
std::optional<vec2i> pos;
std::string title;
SDL_WindowFlags flags{};
bool vulkan_window = true;
bool visible_on_create = true;
};
class window : public object {
public:
MIRAI_OBJECT_TYPE_INFO(window, object)
window() = default;
void_result_t make_window(window_config config);
void update(duration_ms delta_time);
auto get_window_id() const {
return SDL_GetWindowID(window_);
}
void show_window(bool show);
void move_window(vec2i pos);
vec2i get_pos() const;
auto is_closing() const {
return closing_;
}
protected:
void on_created() override;
void on_destroying() override;
private:
SDL_Window* window_{};
bool closing_{false};
};
}

View File

@@ -0,0 +1,88 @@
#include "window_manager.h"
#include <SDL3/SDL_vulkan.h>
#include "core/logger.h"
namespace mirai {
window_manager::window_manager(const window_manager_config& config) {
auto result = SDL_Init(SDL_INIT_VIDEO);
if (!result) {
MIRAI_LOG_ERROR("初始化SDL失败: {}", SDL_GetError());
return;
}
// 检查是否已经加载了 Vulkan 库
if (!SDL_Vulkan_LoadLibrary(nullptr)) {
MIRAI_LOG_ERROR("加载 Vulkan 库失败: {}", SDL_GetError());
return;
}
create_main_window(config.main_window);
}
bool window_manager::quit() const {
return main_window_->is_closing();
}
auto window_manager::get_window_by_id(SDL_WindowID id) const -> std::weak_ptr<window> {
const auto result = std::ranges::find_if(windows_, [&](const auto& w) {
return w->get_window_id() == id;
});
if (result != windows_.end()) {
return *result;
}
return {};
}
result_t<std::weak_ptr<window>> window_manager::create_window(window_config config) {
auto win = make_obj<window>();
auto result = win->make_window(config);
if (!result) {
return MAKE_ERROR_INFO(error_code::window_creation_failed, "创建窗口失败: {}", result.error().full_description());
}
windows_.push_back(win);
win->show_window(config.visible_on_create);
win->move_window(config.pos.value_or(vec2i{SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED}));
return win;
}
void window_manager::destroy_window(std::shared_ptr<window> win) {
std::erase(windows_, win);
if (win == main_window_) {
main_window_.reset();
}
}
void window_manager::update(duration_ms delta_time) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) {
g_quit_requested = true;
}
}
for (auto w : windows_) {
w->update(delta_time);
}
}
void window_manager::on_created() {
object::on_created();
}
void window_manager::on_destroying() {
object::on_destroying();
SDL_Quit();
}
void window_manager::create_main_window(const window_config& config) {
auto result = create_window(config);
if (!result) {
MIRAI_LOG_ERROR("无法创建主窗口: {}", result.error().full_description());
return;
}
main_window_ = result.value().lock();
}
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <SDL3/SDL.h>
#include "window.h"
#include "core/object.h"
#include "render/error.h"
namespace mirai {
struct window_manager_config {
window_config main_window;
};
class window_manager : public object {
public:
MIRAI_OBJECT_TYPE_INFO(window_manager, object)
window_manager(const window_manager_config& config = {});
bool quit() const;
auto get_window_by_id(SDL_WindowID id) const -> std::weak_ptr<window>;
result_t<std::weak_ptr<window>> create_window(window_config config);
void destroy_window(std::shared_ptr<window> win);
auto get_main_window() const {
return main_window_;
}
auto get_windows() const {
return windows_;
}
void update(duration_ms delta_time);
protected:
void on_created() override;
void on_destroying() override;
void create_main_window(const window_config& config);
private:
std::vector<std::shared_ptr<window>> windows_;
std::shared_ptr<window> main_window_;
};
}

View File

@@ -10,7 +10,8 @@
"dependencies": [
{
"name": "sdl3",
"version>=": "3.2.28"
"version>=": "3.2.28",
"features": [ "vulkan" ]
},
{
"name": "spirv-cross",