diff --git a/CMakeLists.txt b/CMakeLists.txt index c850ead..12ae68b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,10 +34,3 @@ add_subdirectory(third_party/yoga/yoga) add_subdirectory(src) add_subdirectory(example) # add_subdirectory(tests) - -# 检查编译器是否是 MSVC -if(MSVC) - # 为所有目标添加 /utf-8 编译选项 - add_compile_options(/utf-8) - add_compile_options(/source-charset:utf-8) -endif() \ No newline at end of file diff --git a/plans/design_vulkan_queues.md b/plans/design_vulkan_queues.md deleted file mode 100644 index 8f7e6c2..0000000 --- a/plans/design_vulkan_queues.md +++ /dev/null @@ -1,242 +0,0 @@ -# Vulkan 队列封装设计方案 - -## 1. 背景与目标 - -当前 `src/render/vulkan_device` 在初始化时虽然创建了必要的 Vulkan 队列(图形、计算、传输),但并未将这些队列句柄保存或封装暴露给上层使用。目前的实现中,`transfer_task` 等组件需要使用队列时无法方便地获取。 - -本设计旨在封装 `vulkan_graphics_queue`、`vulkan_compute_queue` 和 `vulkan_transfer_queue`,明确它们的职责,提供统一的提交和同步接口,并集成到 `vulkan_device` 的初始化流程中。 - -## 2. 类结构设计 - -### 2.1 类图概览 - -```mermaid -classDiagram - class object { - <> - } - - class vulkan_queue { - -vk::Queue queue - -u32 family_index - -u32 queue_index - -vk::Device device - +submit(...) - +wait_idle() - +get_handle() - +get_family_index() - } - - class vulkan_graphics_queue { - +submit(command_buffers, wait_semaphores, signal_semaphores, fence) - } - - class vulkan_compute_queue { - +submit(command_buffers, wait_semaphores, signal_semaphores, fence) - } - - class vulkan_transfer_queue { - +submit(command_buffers, wait_semaphores, signal_semaphores, fence) - } - - class vulkan_device { - -graphics_queue: shared_ptr~vulkan_graphics_queue~ - -compute_queue: shared_ptr~vulkan_compute_queue~ - -transfer_queue: shared_ptr~vulkan_transfer_queue~ - +get_graphics_queue() - +get_compute_queue() - +get_transfer_queue() - } - - object <|-- vulkan_queue - vulkan_queue <|-- vulkan_graphics_queue - vulkan_queue <|-- vulkan_compute_queue - vulkan_queue <|-- vulkan_transfer_queue - vulkan_device *-- vulkan_graphics_queue - vulkan_device *-- vulkan_compute_queue - vulkan_device *-- vulkan_transfer_queue -``` - -### 2.2 核心类定义 - -建议在 `src/render/vulkan_queue.h` 中定义。 - -#### 基类 `vulkan_queue` - -作为所有队列的基类,继承自 `mirai::object`。 - -```cpp -namespace mirai { - class vulkan_queue : public object { - MIRAI_OBJECT_TYPE_INFO(vulkan_queue, object) - public: - vulkan_queue(vk::Device device, vk::Queue queue, u32 family_index, u32 queue_index); - - // 基础提交接口 - void submit( - const std::vector& command_buffers, - const std::vector& wait_semaphores = {}, - const std::vector& wait_stages = {}, - const std::vector& signal_semaphores = {}, - vk::Fence fence = nullptr - ); - - // Vulkan 1.3 Synchronization2 提交接口 (建议支持) - void submit2( - const std::vector& command_buffers, - const std::vector& wait_semaphores = {}, - const std::vector& signal_semaphores = {}, - vk::Fence fence = nullptr - ); - - void wait_idle(); - - [[nodiscard]] vk::Queue get_handle() const noexcept { return queue_; } - [[nodiscard]] u32 get_family_index() const noexcept { return family_index_; } - [[nodiscard]] u32 get_queue_index() const noexcept { return queue_index_; } - - protected: - vk::Device device_; - vk::Queue queue_; - u32 family_index_; - u32 queue_index_; - }; -} -``` - -#### 子类实现 - -子类主要用于类型区分,未来可以针对不同类型的队列添加特定的辅助方法(例如图形队列的呈现操作)。 - -* **`vulkan_graphics_queue`**: 处理图形渲染命令。 -* **`vulkan_compute_queue`**: 处理计算着色器命令。 -* **`vulkan_transfer_queue`**: 处理缓冲/图像拷贝命令。 - -```cpp -namespace mirai { - class vulkan_graphics_queue : public vulkan_queue { - MIRAI_OBJECT_TYPE_INFO(vulkan_graphics_queue, vulkan_queue) - public: - using vulkan_queue::vulkan_queue; - // 未来可添加 present 相关接口 - }; - - class vulkan_compute_queue : public vulkan_queue { - MIRAI_OBJECT_TYPE_INFO(vulkan_compute_queue, vulkan_queue) - public: - using vulkan_queue::vulkan_queue; - }; - - class vulkan_transfer_queue : public vulkan_queue { - MIRAI_OBJECT_TYPE_INFO(vulkan_transfer_queue, vulkan_queue) - public: - using vulkan_queue::vulkan_queue; - }; -} -``` - -## 3. 集成与初始化 - -### 3.1 修改 `vulkan_device` - -在 `src/render/vulkan_device.h` 中添加成员变量和访问器。 - -```cpp -class vulkan_device : public object { - // ... 现有代码 ... -public: - // ... - [[nodiscard]] std::shared_ptr get_graphics_queue() const noexcept { return graphics_queue_; } - [[nodiscard]] std::shared_ptr get_compute_queue() const noexcept { return compute_queue_; } - [[nodiscard]] std::shared_ptr get_transfer_queue() const noexcept { return transfer_queue_; } - -private: - std::shared_ptr graphics_queue_; - std::shared_ptr compute_queue_; - std::shared_ptr transfer_queue_; - // ... -}; -``` - -### 3.2 初始化逻辑 - -在 `src/render/vulkan_device.cpp` 的构造函数中,在逻辑设备创建成功后,初始化这些队列对象。 - -**注意:** `find_queue_families` 可能会返回相同的索引给不同类型的队列(例如图形队列通常也支持计算和传输)。如果是相同的 `family_index`,且我们只创建了一个队列 (`queueCount = 1`),那么这些封装对象将共享同一个底层的 `vk::Queue` 句柄。这在 Vulkan 中是完全合法的,但需要注意线程安全(`vkQueueSubmit` 需要外部同步)。 - -建议的初始化流程: - -1. 获取 `unique_queue_families` 并创建逻辑设备(现有逻辑)。 -2. 从 `device_` 获取各个 queue handle。 -3. 创建 `vulkan_*_queue` 实例。 - -```cpp -// 在 vulkan_device 构造函数中: - -// ... 逻辑设备创建之后 ... - -const auto& indices = config.physical_device_info.queue_families; // 假设 config 传入了 indices 或者重新查询 - -// 获取 Graphics Queue -if (indices.graphics_family.has_value()) { - u32 family_index = indices.graphics_family.value(); - vk::Queue queue = device_.getQueue(family_index, 0); - graphics_queue_ = make_obj(device_, queue, family_index, 0); -} - -// 获取 Compute Queue -if (indices.compute_family.has_value()) { - u32 family_index = indices.compute_family.value(); - // 注意:如果 compute family 和 graphics family 相同,这里获得的 queue handle 也是相同的 - vk::Queue queue = device_.getQueue(family_index, 0); - compute_queue_ = make_obj(device_, queue, family_index, 0); -} - -// 获取 Transfer Queue -if (indices.transfer_family.has_value()) { - u32 family_index = indices.transfer_family.value(); - vk::Queue queue = device_.getQueue(family_index, 0); - transfer_queue_ = make_obj(device_, queue, family_index, 0); -} -``` - -*注意:目前的 `vulkan_device_config` 并没有包含 `queue_family_indices`,需要从 `physical_device` 重新获取或者修改 `config` 结构体传入。考虑到 `find_queue_families` 已经在 `vulkan_device` 构造函数中调用了一次,可以直接复用其结果。* - -## 4. 同步与线程安全 - -* **队列提交同步**:`vkQueueSubmit` 是非线程安全的。如果多个 `vulkan_*_queue` 共享同一个底层 `vk::Queue`,则它们在不同线程并发调用 `submit` 时会发生冲突。 -* **解决方案**:可以在 `vulkan_queue` 中添加一个 `std::mutex`。但由于多个 `vulkan_queue` 对象可能共享同一个底层句柄,互斥锁需要是针对底层句柄的。 - * **方案 A (简化版)**:假设应用层逻辑保证不会多线程并发访问同一个底层队列(例如只在渲染线程提交)。 - * **方案 B (共享锁)**:由于我们设计上允许 `graphics` 和 `compute` 是不同的对象但可能指向同一硬件队列,最稳妥的方式是在 `vulkan_device` 中维护一个 `mutex` map,或者简单地如果 family index 相同,则应当共享锁。 - * **方案 C (即时获取)**:`vulkan_device` 内部维护 `vk::Queue` 到 `std::mutex` 的映射。 - -鉴于当前项目处于早期阶段,且 Mirai 引擎可能有特定的线程模型,建议**方案 A** 作为起点,并在 `vulkan_queue` 的文档中明确注明:**如果多个逻辑队列映射到同一个硬件队列,多线程提交需要外部同步。** 或者,如果 `vulkan_queue` 是轻量级封装,我们可以在 `vulkan_device` 这一层控制,确保对于同一个 family index 创建的 `vk::Queue`,只创建一个 `vulkan_queue` 实例并在内部复用,但这样 `get_graphics_queue` 和 `get_compute_queue` 可能会返回同一个对象,这会破坏类型系统(不能同时是 `graphics_queue` 和 `compute_queue`)。 - -**修正方案**: -保持 `vulkan_graphics_queue` 等类型区分。如果底层队列相同,它们就是指向同一资源的各类“视图”。为了线程安全,可以在 `vulkan_device` 中为每个 unique queue family 创建一个 `std::mutex`,并在构造 `vulkan_queue` 时传入这个 mutex 的指针/引用。 - -```cpp -class vulkan_queue : public object { - // ... - std::shared_ptr lock_; // 指向该硬件队列的锁 -public: - void submit(...) { - std::lock_guard lock(*lock_); - queue_.submit(...); - } -}; -``` - -在 `vulkan_device` 初始化时: -1. 建立 `family_index -> shared_ptr` 的映射。 -2. 创建 `vulkan_*_queue` 时,根据其 `family_index` 传入对应的 mutex。 - -这样即使 `graphics_queue` 和 `compute_queue` 是不同的对象,只要它们对应同一个硬件队列,就会争夺同一个锁,从而保证线程安全。 - -## 5. 实现计划 - -1. 创建 `src/render/vulkan_queue.h` 和 `src/render/vulkan_queue.cpp`。 -2. 实现 `vulkan_queue` 及其子类。 -3. 在 `vulkan_device.h` 中引入头文件并添加成员。 -4. 修改 `vulkan_device.cpp` 的构造函数,实现队列的获取和封装对象的创建,并处理 mutex 共享逻辑。 -5. 修改 `find_queue_families` 的调用逻辑,确保能获取到索引以便初始化。 diff --git a/src/app/mirai_app.cpp b/src/app/mirai_app.cpp index 192e04b..bfd72cf 100644 --- a/src/app/mirai_app.cpp +++ b/src/app/mirai_app.cpp @@ -31,6 +31,7 @@ namespace mirai { wgpu_context::instance().update(); update_time(); + std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 240)); } shutdown(); diff --git a/src/render/test_shader_code.wgsl b/src/render/test_shader_code.wgsl new file mode 100644 index 0000000..7b48094 --- /dev/null +++ b/src/render/test_shader_code.wgsl @@ -0,0 +1,19 @@ +// 顶点着色器 (Vertex Shader) +@vertex +fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { + // 简单的三角形顶点数据 + var pos = array, 3>( + vec2(0.0, 0.5), + vec2(-0.5, -0.5), + vec2(0.5, -0.5) + ); + + return vec4(pos[in_vertex_index], 0.0, 1.0); +} + +// 片段着色器 (Fragment Shader) +@fragment +fn fs_main() -> @location(0) vec4 { + // 输出红色 + return vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/src/render/wgpu_context.cpp b/src/render/wgpu_context.cpp index 60919a7..453beb7 100644 --- a/src/render/wgpu_context.cpp +++ b/src/render/wgpu_context.cpp @@ -2,10 +2,41 @@ #include "core/logger.h" +#include +#include + namespace mirai { + void test_shader() { + std::filesystem::path shader_path = R"(F:\Projects\mirai\src\render\test_shader_code.wgsl)"; + std::ifstream shader_file(shader_path, std::ios::in); + if (!shader_file.is_open()) { + MIRAI_LOG_ERROR("无法打开测试着色器文件: {}", shader_path.string()); + return; + } + std::string shader_code((std::istreambuf_iterator(shader_file)), std::istreambuf_iterator()); + MIRAI_LOG_INFO("测试着色器代码:\n{}", shader_code); + + wgpu::ShaderModuleWGSLDescriptor wgpu_shader_module_descriptor{}; + wgpu_shader_module_descriptor.setDefault(); + wgpu_shader_module_descriptor.code = shader_code.c_str(); + + wgpu::ShaderModuleDescriptor shader_module_descriptor{}; + shader_module_descriptor.setDefault(); + shader_module_descriptor.label = "Test shader module"; + shader_module_descriptor.nextInChain = &wgpu_shader_module_descriptor.chain; + + wgpu::ShaderModule shader_module = wgpu_context::instance().get_device().createShaderModule(shader_module_descriptor); + if (!shader_module) { + MIRAI_LOG_ERROR("无法创建测试着色器模块"); + return; + } + MIRAI_LOG_INFO("测试着色器模块创建成功"); + } + bool wgpu_context::setup() { // We create a descriptor wgpu::InstanceDescriptor desc{}; + desc.setDefault(); // We create the instance using this descriptor wgpu_instance_ = wgpu::createInstance(desc); @@ -29,11 +60,13 @@ namespace mirai { bool wgpu_context::create_default_device(wgpu::Surface compatible_surface) { { wgpu::RequestAdapterOptions options{}; + options.setDefault(); options.powerPreference = wgpu::PowerPreference::HighPerformance; options.compatibleSurface = compatible_surface; wgpu::raii::Adapter adapter = wgpu_instance_.requestAdapter(options); wgpu::DeviceDescriptor device_desc{}; + device_desc.setDefault(); device_desc.label = "Default device"; device_desc.defaultQueue.label = "Default queue"; device_desc.deviceLostCallback = [](WGPUDeviceLostReason reason, char const* message, void* userdata) { @@ -49,12 +82,13 @@ namespace mirai { MIRAI_LOG_ERROR("未捕获的错误 {}: {}", static_cast(type), message); }); - + test_shader(); return true; } wgpu::CommandEncoder wgpu_context::get_command_encoder() { wgpu::CommandEncoderDescriptor encoder_desc{}; + encoder_desc.setDefault(); encoder_desc.label = "Main command encoder"; return wgpu_device_.createCommandEncoder(encoder_desc); } diff --git a/src/window/window.cpp b/src/window/window.cpp index 77701d7..e13d6c8 100644 --- a/src/window/window.cpp +++ b/src/window/window.cpp @@ -47,16 +47,18 @@ namespace mirai { } } wgpu::raii::CommandEncoder encoder = wgpu_context::instance().get_command_encoder(); - wgpu::raii::Queue queue = wgpu_context::instance().get_queue(); - auto [texture, texture_view] = acquire_next_swapchain_texture(); + wgpu::raii::Queue queue = wgpu_context::instance().get_queue(); + auto [texture, texture_view] = acquire_next_swapchain_texture(); wgpu::RenderPassColorAttachment color_attachment{}; + color_attachment.setDefault(); color_attachment.view = texture_view; color_attachment.loadOp = wgpu::LoadOp::Clear; color_attachment.storeOp = wgpu::StoreOp::Store; color_attachment.clearValue = {0.95f, 0.35f, 0.49f, 1.0f}; wgpu::RenderPassDescriptor render_pass_descriptor{}; + render_pass_descriptor.setDefault(); render_pass_descriptor.colorAttachmentCount = 1; render_pass_descriptor.colorAttachments = &color_attachment; @@ -137,6 +139,7 @@ namespace mirai { void_result_t window::rebuild_swapchain(bool hdr_enabled, vec2i new_extent) { wgpu::SurfaceConfiguration wgpu_surface_config{}; + wgpu_surface_config.setDefault(); wgpu_surface_config.usage = wgpu::TextureUsage::RenderAttachment; wgpu_surface_config.device = wgpu_context::instance().get_device(); wgpu_surface_config.format = hdr_enabled ? wgpu::TextureFormat::RGB10A2Unorm : wgpu::TextureFormat::BGRA8Unorm; @@ -157,6 +160,7 @@ namespace mirai { return {swapchain_texture, nullptr}; } wgpu::TextureViewDescriptor view_desc{}; + view_desc.setDefault(); view_desc.label = "Surface Texture View"; view_desc.format = wgpuTextureGetFormat(swapchain_texture.texture); view_desc.dimension = wgpu::TextureViewDimension::_2D;