diff --git a/example/main.cpp b/example/main.cpp index 8a3b3be..fe3f94e 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -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; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7c80f3d..49b392c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/app/mirai_app.cpp b/src/app/mirai_app.cpp new file mode 100644 index 0000000..18bd6ba --- /dev/null +++ b/src/app/mirai_app.cpp @@ -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(config.window_mgr_config); + vulkan_instance_ = make_obj(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; + } +} diff --git a/src/app/mirai_app.h b/src/app/mirai_app.h new file mode 100644 index 0000000..3ad3099 --- /dev/null +++ b/src/app/mirai_app.h @@ -0,0 +1,27 @@ +#pragma once +#include + +#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_mgr; + std::shared_ptr vulkan_instance_; + }; +} diff --git a/src/core/object.h b/src/core/object.h index 10c01c9..6d844ad 100644 --- a/src/core/object.h +++ b/src/core/object.h @@ -223,6 +223,9 @@ namespace mirai { friend struct mirai::object_deleter; \ friend class object_registry; + template + concept is_object_type = std::derived_from; + struct object_factory { template requires std::derived_from static auto create(Args&&... args) { @@ -232,7 +235,7 @@ namespace mirai { } }; - template requires std::derived_from + template auto make_obj(Args&&... args) { return object_factory::create(std::forward(args)...); } diff --git a/src/core/time_util.h b/src/core/time_util.h new file mode 100644 index 0000000..d35d65e --- /dev/null +++ b/src/core/time_util.h @@ -0,0 +1,28 @@ +#pragma once +#include "types/types.h" + +namespace mirai { + inline time_point last_time_point; + + template + auto epoch_time() { + return std::chrono::time_point_cast(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; + } +} \ No newline at end of file diff --git a/src/render/allocator.cpp b/src/render/allocator.cpp new file mode 100644 index 0000000..30c4efa --- /dev/null +++ b/src/render/allocator.cpp @@ -0,0 +1 @@ +#include "allocator.h" diff --git a/src/render/allocator.h b/src/render/allocator.h new file mode 100644 index 0000000..8237767 --- /dev/null +++ b/src/render/allocator.h @@ -0,0 +1,125 @@ +#pragma once +#include "types/types.h" +#include "resource_types.h" +#include "core/object.h" + +#include +#include + +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; + + }; +} diff --git a/src/render/error.h b/src/render/error.h new file mode 100644 index 0000000..6ab921a --- /dev/null +++ b/src/render/error.h @@ -0,0 +1,348 @@ +#pragma once + +#include "types/types.h" + +#include +#include +#include +#include +#include + +/** + * @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 +using result_t = std::expected; + +using void_result_t = result_t; + +#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() \ +) diff --git a/src/render/resource_types.h b/src/render/resource_types.h new file mode 100644 index 0000000..8ba535b --- /dev/null +++ b/src/render/resource_types.h @@ -0,0 +1,538 @@ +#pragma once +#include + +#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 简单的低精度法线贴图 (仅存XY,Z重建) + 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(usage_bytes) / static_cast(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 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(unused_bytes) / static_cast(total) : 0.0; + } + }; +} diff --git a/src/render/resource_types_vulkan.h b/src/render/resource_types_vulkan.h new file mode 100644 index 0000000..2f19c02 --- /dev/null +++ b/src/render/resource_types_vulkan.h @@ -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; + } + } +} diff --git a/src/render/vulkan_instance.cpp b/src/render/vulkan_instance.cpp new file mode 100644 index 0000000..98d2d68 --- /dev/null +++ b/src/render/vulkan_instance.cpp @@ -0,0 +1,359 @@ +#include "vulkan_instance.h" + +#include "vulkan_types.h" +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE + +#include +#include +#include + +#include "core/logger.h" + +namespace mirai { + #if MIRAI_DEBUG + /// Vulkan 验证层名称 + constexpr const char* validation_layer_name = "VK_LAYER_KHRONOS_validation"; + + /// 验证层列表 + constexpr std::array validation_layers = { + validation_layer_name + }; + #endif + + /// 必需的设备扩展 + constexpr std::array required_device_extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME + }; + + /// 可选的设备扩展(用于额外功能) + constexpr std::array 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 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 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("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(enabled_extensions_.size())) + .setPpEnabledExtensionNames(enabled_extensions_.data()) + .setEnabledLayerCount(static_cast(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 { + std::vector 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 unique_extensions; + std::vector 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(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( + 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(func(static_cast(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( + vkGetInstanceProcAddr(instance_, "vkDestroyDebugUtilsMessengerEXT")); + + if (func) { + func(static_cast(instance_), static_cast(debug_messenger_), nullptr); + } + debug_messenger_ = nullptr; + } + #endif +} diff --git a/src/render/vulkan_instance.h b/src/render/vulkan_instance.h new file mode 100644 index 0000000..d499687 --- /dev/null +++ b/src/render/vulkan_instance.h @@ -0,0 +1,158 @@ +#pragma once +#include "core/object.h" +#include "error.h" + +#include +#include + +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; + + /** + * @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 extra_extensions; + + /// 额外的验证层 + std::vector 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& get_enabled_extensions() const noexcept { + return enabled_extensions_; + } + + /** + * @brief 获取已启用的验证层列表 + * @return 层名称列表 + */ + [[nodiscard]] const std::vector& 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 get_available_extensions(); + + /** + * @brief 获取所有可用的验证层 + * @return 层属性列表 + */ + [[nodiscard]] static std::vector 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; + + vk::Instance instance_; + bool validation_enabled_{MIRAI_DEBUG}; + u32 api_version_{VK_API_VERSION_1_3}; + std::vector enabled_extensions_; + std::vector 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 + }; +} diff --git a/src/render/vulkan_types.h b/src/render/vulkan_types.h new file mode 100644 index 0000000..3670ccb --- /dev/null +++ b/src/render/vulkan_types.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#if VK_HEADER_VERSION >= 304 + using vk_dynamic_loader = vk::detail::DynamicLoader; +#else + using vk_dynamic_loader = vk::DynamicLoader; +#endif diff --git a/src/types/types.h b/src/types/types.h index d9ce876..472cbb1 100644 --- a/src/types/types.h +++ b/src/types/types.h @@ -3,6 +3,9 @@ #include #include #include +#include + +#include 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; using const_byte_span = std::span; + +using duration_ns = std::chrono::duration; +using duration_ms = std::chrono::duration; +using duration_s = std::chrono::duration>; +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; +using rect2d = Eigen::AlignedBox2f; + +inline bool g_quit_requested = false; diff --git a/src/window/window.cpp b/src/window/window.cpp new file mode 100644 index 0000000..44cbf8d --- /dev/null +++ b/src/window/window.cpp @@ -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; + } +} diff --git a/src/window/window.h b/src/window/window.h new file mode 100644 index 0000000..58d287c --- /dev/null +++ b/src/window/window.h @@ -0,0 +1,44 @@ +#pragma once +#include "core/object.h" +#include + +#include "render/error.h" + +namespace mirai { + struct window_config { + vec2i size; + // 窗口位置, 空表示默认位置(SDL_WINDOWPOS_CENTERED) + std::optional 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}; + }; +} diff --git a/src/window/window_manager.cpp b/src/window/window_manager.cpp new file mode 100644 index 0000000..8a8cb15 --- /dev/null +++ b/src/window/window_manager.cpp @@ -0,0 +1,88 @@ +#include "window_manager.h" + +#include + +#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 { + 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> window_manager::create_window(window_config config) { + auto win = make_obj(); + 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 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(); + } +} diff --git a/src/window/window_manager.h b/src/window/window_manager.h new file mode 100644 index 0000000..290d7ef --- /dev/null +++ b/src/window/window_manager.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#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; + result_t> create_window(window_config config); + void destroy_window(std::shared_ptr 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> windows_; + std::shared_ptr main_window_; + }; +} diff --git a/vcpkg.json b/vcpkg.json index 27ddcbb..9e259ee 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -10,7 +10,8 @@ "dependencies": [ { "name": "sdl3", - "version>=": "3.2.28" + "version>=": "3.2.28", + "features": [ "vulkan" ] }, { "name": "spirv-cross",