添加渲染管线实现及着色器文件热重载功能

This commit is contained in:
2026-01-19 18:22:58 +08:00
parent 25a4df2d1d
commit 4357ecaa26
17 changed files with 501 additions and 73 deletions

3
.gitmodules vendored
View File

@@ -28,3 +28,6 @@
[submodule "third_party/fmt"]
path = third_party/fmt
url = https://github.com/fmtlib/fmt.git
[submodule "third_party/efsw"]
path = third_party/efsw
url = https://github.com/SpartanJ/efsw.git

View File

@@ -30,6 +30,7 @@ add_subdirectory(third_party/SDL)
add_subdirectory(third_party/spdlog)
add_subdirectory(third_party/stb)
add_subdirectory(third_party/yoga/yoga)
add_subdirectory(third_party/efsw)
add_subdirectory(src)
add_subdirectory(example)

View File

@@ -17,6 +17,7 @@ target_link_libraries(${PROJECT_NAME} PUBLIC
stb
fmt::fmt-header-only
webgpu
efsw-static
)
target_link_directories(${PROJECT_NAME} PUBLIC ${Stb_INCLUDE_DIR})
target_compile_definitions(${PROJECT_NAME} PUBLIC VULKAN_HPP_NO_EXCEPTIONS VMA_NOT_NULL= VMA_NULLABLE=)

View File

@@ -0,0 +1,90 @@
#include "render_pipeline.h"
#include "shader_module.h"
#include "core/logger.h"
namespace mirai {
render_pipeline::render_pipeline(const std::shared_ptr<shader_module>& shader_module, wgpu::Device device) {
shader_module_ = shader_module;
device_ = device;
}
render_pipeline::~render_pipeline() {
if (render_pipeline_) {
render_pipeline_.release();
}
if (pipeline_layout_) {
pipeline_layout_.release();
}
}
bool render_pipeline::begin_rebuild_pipeline(wgpu::Device device) {
render_pipeline_descriptor_.setDefault();
if (!pipeline_layout_) {
wgpu::PipelineLayoutDescriptor pipeline_layout_descriptor{};
pipeline_layout_descriptor.setDefault();
pipeline_layout_descriptor.label = "Render pipeline layout";
pipeline_layout_ = device.createPipelineLayout(pipeline_layout_descriptor);
if (!pipeline_layout_) {
MIRAI_LOG_ERROR("无法创建渲染管线布局");
return false;
}
}
render_pipeline_descriptor_.label = "Render pipeline";
render_pipeline_descriptor_.layout = pipeline_layout_;
wgpu::PrimitiveState primitive_state{};
primitive_state.setDefault();
primitive_state.topology = wgpu::PrimitiveTopology::TriangleList;
primitive_state.frontFace = wgpu::FrontFace::CCW;
primitive_state.cullMode = wgpu::CullMode::None;
wgpu::MultisampleState multisample_state{};
multisample_state.setDefault();
multisample_state.count = 1;
multisample_state.mask = 0xFFFFFFFF;
multisample_state.alphaToCoverageEnabled = false;
render_pipeline_descriptor_.primitive = primitive_state;
render_pipeline_descriptor_.multisample = multisample_state;
shader_module_->modify_pipeline_descriptor(render_pipeline_descriptor_);
return true;
}
bool render_pipeline::end_rebuild_pipeline(wgpu::Device device) {
if (render_pipeline_) {
render_pipeline_.release();
render_pipeline_ = nullptr;
}
render_pipeline_ = device.createRenderPipeline(render_pipeline_descriptor_);
if (!render_pipeline_) {
MIRAI_LOG_ERROR("无法创建渲染管线");
return false;
}
return true;
}
wgpu::RenderPipeline render_pipeline::get_pipeline() {
process_hot_reload();
return render_pipeline_;
}
void render_pipeline::init() {
shader_module_->reload_shader_module();
begin_rebuild_pipeline(device_);
if (modify_descriptor_)
modify_descriptor_(render_pipeline_descriptor_);
end_rebuild_pipeline(device_);
}
void render_pipeline::process_hot_reload() {
if (shader_module_->is_need_reload()) {
MIRAI_LOG_INFO("渲染管线检测到着色器模块已修改,正在重新加载...");
init();
MIRAI_LOG_INFO("渲染管线重新加载完成");
shader_module_->process_reload();
}
}
}

View File

@@ -0,0 +1,35 @@
#pragma once
#include <webgpu/webgpu.hpp>
#include "core/object.h"
namespace mirai {
class shader_module;
class render_pipeline : public object {
MIRAI_OBJECT_TYPE_INFO(render_pipeline, object)
public:
render_pipeline(const std::shared_ptr<shader_module>& shader_module, wgpu::Device device);
~render_pipeline() override;
void set_modify_descriptor_callback(std::function<void(wgpu::RenderPipelineDescriptor&)> callback) {
modify_descriptor_ = std::move(callback);
}
[[nodiscard]] wgpu::RenderPipeline get_pipeline();
void init();
void process_hot_reload();
private:
bool begin_rebuild_pipeline(wgpu::Device device);
bool end_rebuild_pipeline(wgpu::Device device);
wgpu::Device device_;
wgpu::RenderPipeline render_pipeline_;
wgpu::PipelineLayout pipeline_layout_;
wgpu::RenderPipelineDescriptor render_pipeline_descriptor_;
std::shared_ptr<shader_module> shader_module_;
std::function<void(wgpu::RenderPipelineDescriptor&)> modify_descriptor_;
};
}

View File

@@ -0,0 +1,35 @@
#include "shader_code.h"
#include "core/logger.h"
#include <fstream>
namespace mirai {
static efsw::FileWatcher file_watcher;
shader_file_code::shader_file_code(std::filesystem::path file_path): file_path_(std::move(file_path)) {
// 监听文件是否被修改
file_watcher.addWatch(file_path_.parent_path().string(), this, false);
file_watcher.watch();
}
shader_file_code::~shader_file_code() {
file_watcher.removeWatch(file_path_.parent_path().string());
}
std::string shader_file_code::get_code() const {
std::ifstream shader_file(file_path_, std::ios::in);
if (!shader_file.is_open()) {
MIRAI_LOG_ERROR("无法打开着色器文件: {}", file_path_.string());
return "";
}
std::string shader_code((std::istreambuf_iterator<char>(shader_file)), std::istreambuf_iterator<char>());
return shader_code;
}
void shader_file_code::handleFileAction(efsw::WatchID watchid, const std::string& dir, const std::string& filename,
efsw::Action action, std::string oldFilename) {
// 先更新路径
file_path_ = std::filesystem::path(dir) / filename;
mark_dirty();
}
}

View File

@@ -0,0 +1,71 @@
#pragma once
#include <string>
#include <filesystem>
#include "efsw/efsw.hpp"
#include "render/webgpu-raii.hpp"
#include "core/object.h"
namespace mirai {
class shader_code : public object {
MIRAI_OBJECT_TYPE_INFO(shader_code, object)
public:
virtual ~shader_code() = default;
[[nodiscard]] virtual std::string get_code() const = 0;
virtual std::string get_name() const {
return "Unnamed Shader Code";
}
virtual bool is_need_reload() const { return is_dirty.load(); }
void process_reload() {
is_dirty = false;
}
protected:
void mark_dirty() {
is_dirty = true;
}
std::atomic_bool is_dirty{ false };
};
class shader_file_code : public shader_code, public efsw::FileWatchListener {
MIRAI_OBJECT_TYPE_INFO(shader_file_code, shader_code)
public:
explicit shader_file_code(std::filesystem::path file_path);
~shader_file_code() override;
[[nodiscard]] std::string get_code() const override;
std::string get_name() const override {
return file_path_.filename().string();
}
protected:
void handleFileAction(efsw::WatchID watchid, const std::string& dir, const std::string& filename, efsw::Action action, std::string oldFilename) override;
private:
std::filesystem::path file_path_;
};
class shader_string_code : public shader_code {
MIRAI_OBJECT_TYPE_INFO(shader_string_code, shader_code)
public:
explicit shader_string_code(std::string code)
: code_(std::move(code)) {}
void set_code(const std::string& new_code) {
code_ = new_code;
mark_dirty();
}
[[nodiscard]] std::string get_code() const override {
return code_;
}
void set_name(const std::string& name) {
name_ = name;
}
std::string get_name() const override {
return name_.empty() ? "String Shader Code" : name_;
}
private:
std::string code_;
std::string name_;
};
}

View File

@@ -0,0 +1,69 @@
#include "shader_entry_point.h"
#include <regex>
#include "shader_module.h"
#include "core/logger.h"
namespace mirai {
bool shader_entry_point::auto_set_entry_point_name(std::string_view shader_code) {
if (auto entry_name_opt = get_entry_name(std::string(shader_code), get_stage_name())) {
set_entry_point_name(*entry_name_opt);
return true;
}
return false;
}
void shader_entry_point::modify_pipeline_descriptor(const std::shared_ptr<shader_module> sm,
wgpu::RenderPipelineDescriptor& descriptor) {
if (entry_point_name_.empty()) {
if (!auto_set_entry_point_name(sm->get_code())) {
MIRAI_LOG_WARN("无法为阶段 {} 自动设置入口点名称,请手动设置。", get_stage_name());
}
}
}
std::optional<std::string> shader_entry_point::get_entry_name(const std::string& code_str, std::string_view stage) const {
// 查找类似 "@vertex fn main_vs(" 的模式
std::regex entry_point_regex(
"@" + std::string(stage) + R"(\s+fn\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\()"
);
std::smatch match;
// 使用cbegin()和cend()来安全地搜索std::string
if (std::regex_search(code_str.cbegin(), code_str.cend(), match, entry_point_regex)) {
// match[0] 是整个匹配的字符串 (例如 "@vertex fn main_vs(")
// match[1] 是第一个捕获组 (我们想要的函数名 "main_vs")
if (match.size() >= 2) {
return match[1].str();
}
}
return std::nullopt;
}
void shader_entry_point_vertex::modify_pipeline_descriptor(std::shared_ptr<shader_module> sm,
wgpu::RenderPipelineDescriptor& descriptor) {
shader_entry_point::modify_pipeline_descriptor(sm, descriptor);
wgpu::VertexState vertex_state;
vertex_state.setDefault();
vertex_state.module = sm->get_shader_module();
vertex_state.entryPoint = entry_point_name_.c_str();
descriptor.vertex = vertex_state;
}
void shader_entry_point_fragment::modify_pipeline_descriptor(std::shared_ptr<shader_module> sm,
wgpu::RenderPipelineDescriptor& descriptor) {
shader_entry_point::modify_pipeline_descriptor(sm, descriptor);
fragment_state_.setDefault();
fragment_state_.module = sm->get_shader_module();
fragment_state_.entryPoint = entry_point_name_.c_str();
fragment_state_.targetCount = static_cast<uint32_t>(color_target_states_.size());
fragment_state_.targets = color_target_states_.data();
fragment_state_.constantCount = static_cast<uint32_t>(constant_entries_.size());
fragment_state_.constants = constant_entries_.data();
descriptor.fragment = &fragment_state_;
}
}

View File

@@ -0,0 +1,67 @@
#pragma once
#include <string_view>
#include <string>
#include <optional>
#include <webgpu/webgpu.hpp>
#include "core/object.h"
namespace mirai {
class shader_module;
class shader_entry_point : public object {
MIRAI_OBJECT_TYPE_INFO(shader_entry_point, object)
public:
virtual ~shader_entry_point() = default;
virtual bool auto_set_entry_point_name(std::string_view shader_code);
[[nodiscard]] std::string_view get_entry_point_name() const {
return entry_point_name_;
}
void set_entry_point_name(std::string_view shader_module) {
entry_point_name_ = shader_module;
}
virtual void modify_pipeline_descriptor(std::shared_ptr<shader_module> sm, wgpu::RenderPipelineDescriptor& descriptor);
protected:
std::optional<std::string> get_entry_name(const std::string& code_str, std::string_view stage) const;
virtual std::string_view get_stage_name() const = 0;
std::string entry_point_name_;
};
class shader_entry_point_vertex : public shader_entry_point {
MIRAI_OBJECT_TYPE_INFO(shader_entry_point_vertex, shader_entry_point)
public:
void modify_pipeline_descriptor(std::shared_ptr<shader_module> sm, wgpu::RenderPipelineDescriptor& descriptor) override;
protected:
std::string_view get_stage_name() const override {
return "vertex";
}
};
class shader_entry_point_fragment : public shader_entry_point {
MIRAI_OBJECT_TYPE_INFO(shader_entry_point_fragment, shader_entry_point)
public:
// 添加颜色目标状态 !注意不要存储这个对象, 因为这个对象地址随时会被改变!
auto& add_color_target_state() {
auto& out = color_target_states_.emplace_back();
out.setDefault();
return out;
}
// 添加常量条目 !注意不要存储这个对象, 因为这个对象地址随时会被改变!
auto& add_constant_entry() {
auto& out = constant_entries_.emplace_back();
out.setDefault();
return out;
}
void modify_pipeline_descriptor(std::shared_ptr<shader_module> sm, wgpu::RenderPipelineDescriptor& descriptor) override;
protected:
std::string_view get_stage_name() const override {
return "fragment";
}
wgpu::FragmentState fragment_state_;
std::vector<wgpu::ColorTargetState> color_target_states_;
std::vector<wgpu::ConstantEntry> constant_entries_;
};
}

View File

@@ -0,0 +1,5 @@
#include "shader_manager.h"
namespace mirai {
}

View File

@@ -0,0 +1,7 @@
#pragma once
namespace mirai {
class shader_manager {
};
}

View File

@@ -0,0 +1,59 @@
#include "shader_module.h"
#include "shader_code.h"
#include "core/logger.h"
#include <regex>
#include "shader_entry_point.h"
namespace mirai {
shader_module::shader_module(const wgpu::Device& device, std::shared_ptr<shader_code> code) {
if (!code) {
throw std::runtime_error("shader_module代码为空");
}
device_ = device;
code_ = std::move(code);
}
shader_module::~shader_module() {
if (shader_module_)
shader_module_.release();
}
void shader_module::modify_pipeline_descriptor(wgpu::RenderPipelineDescriptor& descriptor) {
const auto p = shared_this();
for (const auto& entry_point : entry_points_) {
entry_point->modify_pipeline_descriptor(p, descriptor);
}
}
void shader_module::reload_shader_module() {
MIRAI_LOG_INFO("着色器 {} 开始热重载", code_->get_name());
std::string shader_code = code_->get_code();
if (shader_code.empty()) {
MIRAI_LOG_WARN("着色器代码是空的,无法创建着色器模块。");
return;
}
if (shader_module_) {
shader_module_.release();
shader_module_ = nullptr;
}
auto shader_name = "Shader Module: " + code_->get_name();
wgpu::ShaderModuleWGSLDescriptor wgsl_desc{};
wgsl_desc.setDefault();
wgsl_desc.code = shader_code.c_str();
wgpu::ShaderModuleDescriptor shader_module_desc{};
shader_module_desc.setDefault();
shader_module_desc.label = shader_name.c_str();
shader_module_desc.nextInChain = &wgsl_desc.chain;
shader_module_ = device_.createShaderModule(shader_module_desc);
if (!shader_module_) {
MIRAI_LOG_ERROR("无法创建着色器模块 {}", code_->get_name());
return;
}
MIRAI_LOG_INFO("着色器 {} 热重载完成", code_->get_name());
}
}

View File

@@ -0,0 +1,35 @@
#pragma once
#include <filesystem>
#include "shader_code.h"
#include "core/object.h"
#include "render/webgpu-raii.hpp"
namespace mirai {
class shader_entry_point;
class shader_code;
class shader_module : public object {
MIRAI_OBJECT_TYPE_INFO(shader_module, object);
public:
shader_module(const wgpu::Device& device, std::shared_ptr<shader_code> code);
~shader_module() override;
void add_entry_point(const std::shared_ptr<shader_entry_point>& entry_point) {
entry_points_.push_back(entry_point);
}
void modify_pipeline_descriptor(wgpu::RenderPipelineDescriptor& descriptor);
void reload_shader_module();
auto get_shader_module() const { return shader_module_; }
auto get_code() const { return code_->get_code(); }
auto is_need_reload() const { return code_->is_need_reload(); }
void process_reload() const { code_->process_reload(); }
private:
wgpu::Device device_;
wgpu::ShaderModule shader_module_;
std::vector<std::shared_ptr<shader_entry_point>> entry_points_;
std::shared_ptr<shader_code> code_;
};
}

View File

@@ -5,83 +5,29 @@
#include <filesystem>
#include <fstream>
#include "core/object.h"
#include "shader/render_pipeline.h"
#include "shader/shader_code.h"
#include "shader/shader_entry_point.h"
#include "shader/shader_module.h"
namespace mirai {
void wgpu_context::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);
auto code = make_obj<shader_file_code>(shader_path);
auto sm = make_obj<shader_module>(wgpu_device_, code);
wgpu::ShaderModuleWGSLDescriptor wgpu_shader_module_descriptor{};
wgpu_shader_module_descriptor.setDefault();
wgpu_shader_module_descriptor.code = shader_code.c_str();
auto vertex_entry = make_obj<shader_entry_point_vertex>();
auto frga_entry = make_obj<shader_entry_point_fragment>();
auto& color_target = frga_entry->add_color_target_state();
color_target.format = wgpu::TextureFormat::BGRA8Unorm;
color_target.writeMask = wgpu::ColorWriteMask::All;
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;
sm->add_entry_point(vertex_entry);
sm->add_entry_point(frga_entry);
wgpu::ShaderModule shader_module = get_device().createShaderModule(shader_module_descriptor);
if (!shader_module) {
MIRAI_LOG_ERROR("无法创建测试着色器模块");
return;
}
MIRAI_LOG_INFO("测试着色器模块创建成功");
wgpu::ColorTargetState color_target_state{};
color_target_state.setDefault();
color_target_state.format = wgpu::TextureFormat::BGRA8Unorm;
color_target_state.writeMask = wgpu::ColorWriteMask::All;
wgpu::PrimitiveState primitive_state{};
primitive_state.setDefault();
primitive_state.topology = wgpu::PrimitiveTopology::TriangleList;
primitive_state.frontFace = wgpu::FrontFace::CCW;
primitive_state.cullMode = wgpu::CullMode::None;
wgpu::MultisampleState multisample_state{};
multisample_state.setDefault();
multisample_state.count = 1;
multisample_state.mask = 0xFFFFFFFF;
multisample_state.alphaToCoverageEnabled = false;
wgpu::VertexState vertex_state{};
vertex_state.setDefault();
vertex_state.module = shader_module;
vertex_state.entryPoint = "vs_main";
wgpu::FragmentState fragment_state{};
fragment_state.setDefault();
fragment_state.module = shader_module;
fragment_state.entryPoint = "fs_main";
fragment_state.targetCount = 1;
fragment_state.targets = &color_target_state;
wgpu::PipelineLayoutDescriptor pipeline_layout_descriptor{};
pipeline_layout_descriptor.setDefault();
pipeline_layout_descriptor.label = "Test pipeline layout";
wgpu::PipelineLayout pipeline_layout = get_device().createPipelineLayout(pipeline_layout_descriptor);
if (!pipeline_layout) {
MIRAI_LOG_ERROR("无法创建测试管线布局");
return;
}
wgpu::RenderPipelineDescriptor render_pipeline_descriptor{};
render_pipeline_descriptor.setDefault();
render_pipeline_descriptor.label = "Test render pipeline";
render_pipeline_descriptor.layout = pipeline_layout;
render_pipeline_descriptor.vertex = vertex_state;
render_pipeline_descriptor.fragment = &fragment_state;
render_pipeline_descriptor.primitive = primitive_state;
render_pipeline_descriptor.multisample = multisample_state;
render_pipeline_ = get_device().createRenderPipeline(render_pipeline_descriptor);
if (!render_pipeline_) {
MIRAI_LOG_ERROR("无法创建测试渲染管线");
return;
}
render_pipeline_ = make_obj<render_pipeline>(sm, wgpu_device_);
render_pipeline_->init();
}
bool wgpu_context::setup() {

View File

@@ -2,6 +2,8 @@
#include "render/webgpu-raii.hpp"
namespace mirai {
class render_pipeline;
class wgpu_context {
public:
static auto& instance() {
@@ -30,6 +32,6 @@ namespace mirai {
wgpu_context() = default;
wgpu::Instance wgpu_instance_;
wgpu::Device wgpu_device_;
wgpu::RenderPipeline render_pipeline_;
std::shared_ptr<render_pipeline> render_pipeline_;
};
}

View File

@@ -7,6 +7,7 @@
#include "core/logger.h"
#include "render/wgpu_context.h"
#include "render/shader/render_pipeline.h"
namespace mirai {
void_result_t window::make_window(window_config config) {
@@ -65,7 +66,7 @@ namespace mirai {
render_pass_descriptor.colorAttachments = &color_attachment;
wgpu::raii::RenderPassEncoder render_pass = encoder->beginRenderPass(render_pass_descriptor);
render_pass->setPipeline(pipeline);
render_pass->setPipeline(pipeline->get_pipeline());
render_pass->draw(3, 1, 0, 0);
render_pass->end();

1
third_party/efsw vendored Submodule

Submodule third_party/efsw added at 04d17474f1