添加测试着色器功能并优化渲染设置
This commit is contained in:
@@ -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()
|
||||
@@ -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 {
|
||||
<<mirai::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<vk::CommandBuffer>& command_buffers,
|
||||
const std::vector<vk::Semaphore>& wait_semaphores = {},
|
||||
const std::vector<vk::PipelineStageFlags>& wait_stages = {},
|
||||
const std::vector<vk::Semaphore>& signal_semaphores = {},
|
||||
vk::Fence fence = nullptr
|
||||
);
|
||||
|
||||
// Vulkan 1.3 Synchronization2 提交接口 (建议支持)
|
||||
void submit2(
|
||||
const std::vector<vk::CommandBufferSubmitInfo>& command_buffers,
|
||||
const std::vector<vk::SemaphoreSubmitInfo>& wait_semaphores = {},
|
||||
const std::vector<vk::SemaphoreSubmitInfo>& 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<vulkan_graphics_queue> get_graphics_queue() const noexcept { return graphics_queue_; }
|
||||
[[nodiscard]] std::shared_ptr<vulkan_compute_queue> get_compute_queue() const noexcept { return compute_queue_; }
|
||||
[[nodiscard]] std::shared_ptr<vulkan_transfer_queue> get_transfer_queue() const noexcept { return transfer_queue_; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<vulkan_graphics_queue> graphics_queue_;
|
||||
std::shared_ptr<vulkan_compute_queue> compute_queue_;
|
||||
std::shared_ptr<vulkan_transfer_queue> 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<vulkan_graphics_queue>(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<vulkan_compute_queue>(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<vulkan_transfer_queue>(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<std::mutex> lock_; // 指向该硬件队列的锁
|
||||
public:
|
||||
void submit(...) {
|
||||
std::lock_guard<std::mutex> lock(*lock_);
|
||||
queue_.submit(...);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
在 `vulkan_device` 初始化时:
|
||||
1. 建立 `family_index -> shared_ptr<mutex>` 的映射。
|
||||
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` 的调用逻辑,确保能获取到索引以便初始化。
|
||||
@@ -31,6 +31,7 @@ namespace mirai {
|
||||
wgpu_context::instance().update();
|
||||
|
||||
update_time();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 240));
|
||||
}
|
||||
|
||||
shutdown();
|
||||
|
||||
19
src/render/test_shader_code.wgsl
Normal file
19
src/render/test_shader_code.wgsl
Normal file
@@ -0,0 +1,19 @@
|
||||
// 顶点着色器 (Vertex Shader)
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
|
||||
// 简单的三角形顶点数据
|
||||
var pos = array<vec2<f32>, 3>(
|
||||
vec2<f32>(0.0, 0.5),
|
||||
vec2<f32>(-0.5, -0.5),
|
||||
vec2<f32>(0.5, -0.5)
|
||||
);
|
||||
|
||||
return vec4<f32>(pos[in_vertex_index], 0.0, 1.0);
|
||||
}
|
||||
|
||||
// 片段着色器 (Fragment Shader)
|
||||
@fragment
|
||||
fn fs_main() -> @location(0) vec4<f32> {
|
||||
// 输出红色
|
||||
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
@@ -2,10 +2,41 @@
|
||||
|
||||
#include "core/logger.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
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<char>(shader_file)), std::istreambuf_iterator<char>());
|
||||
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<int>(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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user