Compare commits

...

10 Commits

Author SHA1 Message Date
ceba4059de 111 2026-01-04 09:58:08 +08:00
5d9cde18a3 feat(widget): add rounded rectangle mask widget and shader widget base
- Implemented `rounded_rect_mask_widget` for creating rounded rectangle masks with customizable corner radii, feathering, and border width.
- Introduced `shader_widget` and `shader_widget_base` classes for managing shader resources, including pipeline and descriptor set creation.
- Added concepts for shader bindings to ensure type safety and proper usage of shader resources.
- Defined enums for shader widget modes and dirty flags to manage widget state and updates effectively.
- Created utility functions for bitwise operations on dirty flags to simplify state management.
2026-01-02 21:33:03 +08:00
d5d74034be feat: add shader binding template class for {{ shader_name }}
- Introduced {{ snake_name }}_bindings class to encapsulate all binding data for the {{ shader_name }} shader.
- Implemented static checks for shader resources including vertex/fragment shaders, uniform buffers, push constants, and samplers.
- Defined structures for uniform buffers and push constants based on provided configurations.
- Added methods to retrieve SPIR-V bytecode for vertex and fragment shaders.
- Created functions for descriptor set layout and pool creation, handling cases with and without resources.
- Included utility templates for checking the presence of specific samplers and uniform buffers.
2026-01-01 01:43:05 +08:00
a88f43adf8 添加对每个阶段特有资源的支持,优化反射数据的合并和生成逻辑 2025-12-31 23:18:32 +08:00
596f503dfa 重构着色器编译器,支持自动检测多入口点,优化编译流程,更新命令行参数,改进输出文件管理 2025-12-31 23:01:01 +08:00
63bc415857 添加入口函数检查功能,更新相关接口以支持获取入口函数列表 2025-12-31 21:53:35 +08:00
f770bc5225 重构着色器库,添加对现有目标的着色器编译支持,优化参数解析 2025-12-31 21:44:44 +08:00
140f5840e6 更新着色器编译工具,添加对反射数据的合并支持,改进错误处理,优化生成的绑定代码 2025-12-31 21:37:36 +08:00
87e9f316a7 重构着色器库以加载预编译SPIR-V数据
- 移除从源文件加载着色器及热重载功能支持
- 引入直接从预编译SPIR-V数据加载着色器的方法
- 更新着色器模块,消除源路径处理,专注于SPIR-V格式
- 通过移除不必要字段简化着色器库配置
- 调整着色器反射机制以适配预编译SPIR-V数据处理
- 清理相关代码及注释,反映新的加载机制架构
2025-12-31 20:21:17 +08:00
70a0a5117c 着色器编译器 2025-12-31 20:00:46 +08:00
80 changed files with 17665 additions and 5175 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/build
/cmake-*
/.idea
/src/shader/generated

View File

@@ -109,18 +109,26 @@ include(Catch)
# ================================================================================================
# 添加子目录
# ================================================================================================
# 添加工具目录
add_subdirectory(tools/shader_compile)
# 包含着色器编译模块 (必须在 tools/shader_compile 之后)
include(shader_compile)
add_subdirectory(src/core)
add_subdirectory(src/threading)
add_subdirectory(src/window)
add_subdirectory(src/resource)
add_subdirectory(src/shader)
add_subdirectory(src/render)
add_subdirectory(src/widget)
add_subdirectory(src/text)
add_subdirectory(src/input)
add_subdirectory(src/reactive)
add_subdirectory(src/loop)
add_subdirectory(src/ui)
add_subdirectory(src/debug)
add_subdirectory(src/demo)
add_subdirectory(src/app)
# 添加测试目录
@@ -131,6 +139,11 @@ add_subdirectory(tests)
# ================================================================================================
include(GNUInstallDirs)
# 安装 mirai_project_options 接口库(所有其他库都依赖它)
install(TARGETS mirai_project_options
EXPORT mirai-targets
)
install(EXPORT mirai-targets
FILE mirai-targets.cmake
NAMESPACE mirai::

View File

@@ -78,7 +78,7 @@ function(retrieve_files_custom path extension out_files)
# 3. 递归查找所有匹配的文件
file(GLOB_RECURSE found_files
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
RELATIVE ${path}
CONFIGURE_DEPENDS ${file_patterns}
)
@@ -581,7 +581,7 @@ function(simple_executable)
endif()
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC project_options)
target_link_libraries(${PROJECT_NAME} PUBLIC mirai_project_options)
message(STATUS "创建可执行文件目标: ${PROJECT_NAME},引用路径: ${CMAKE_CURRENT_SOURCE_DIR}")
add_os_definitions(${PROJECT_NAME})
endfunction()
@@ -604,13 +604,13 @@ function(simple_library library_type)
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(${PROJECT_NAME} INTERFACE project_options)
target_link_libraries(${PROJECT_NAME} INTERFACE mirai_project_options)
else()
target_include_directories(${PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(${PROJECT_NAME} PUBLIC project_options)
target_link_libraries(${PROJECT_NAME} PUBLIC mirai_project_options)
endif()
message(STATUS "创建库目标: ${PROJECT_NAME},类型: ${library_type},引用路径: ${CMAKE_CURRENT_SOURCE_DIR}")
add_os_definitions(${PROJECT_NAME})

515
cmake/shader_compile.cmake Normal file
View File

@@ -0,0 +1,515 @@
# ============================================================================
# MIRAI 着色器编译 CMake 模块
# ============================================================================
# 提供自动化 GLSL 着色器编译和代码生成功能
# 使用 glslangValidator 或 glslc (shaderc) 编译 GLSL 到 SPIR-V
#
# 主要函数:
# add_glsl_shader_library() - 创建 GLSL 着色器库目标
# compile_glsl_shader() - 编译单个 GLSL 着色器
#
# 辅助函数:
# find_python_with_jinja2() - 查找带 Jinja2 的 Python
# find_glsl_compiler() - 查找 GLSL 编译器
# ============================================================================
include_guard(GLOBAL)
# ============================================================================
# find_python_with_jinja2 - 查找带 Jinja2 模块的 Python
# ============================================================================
function(find_python_with_jinja2 OUT_PYTHON_EXECUTABLE)
# 首先查找 Python
find_package(Python3 COMPONENTS Interpreter QUIET)
if(NOT Python3_FOUND)
message(FATAL_ERROR "Python3 not found. Please install Python 3.")
endif()
# 检查 Jinja2 是否已安装
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "import jinja2; print(jinja2.__version__)"
RESULT_VARIABLE JINJA2_CHECK_RESULT
OUTPUT_VARIABLE JINJA2_VERSION
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT JINJA2_CHECK_RESULT EQUAL 0)
message(FATAL_ERROR
"Python Jinja2 module not found.\n"
"Please install it with: ${Python3_EXECUTABLE} -m pip install jinja2"
)
endif()
message(STATUS "Found Python with Jinja2: ${Python3_EXECUTABLE} (Jinja2 ${JINJA2_VERSION})")
set(${OUT_PYTHON_EXECUTABLE} "${Python3_EXECUTABLE}" PARENT_SCOPE)
endfunction()
# ============================================================================
# find_glsl_compiler - 查找 GLSL 编译器 (glslangValidator 或 glslc)
# ============================================================================
function(find_glsl_compiler OUT_COMPILER OUT_COMPILER_TYPE)
# 首先尝试查找 glslc (shaderc)
find_program(GLSLC_EXECUTABLE glslc
HINTS
$ENV{VULKAN_SDK}/Bin
$ENV{VULKAN_SDK}/bin
${Vulkan_GLSLC_EXECUTABLE}
)
if(GLSLC_EXECUTABLE)
message(STATUS "Found GLSL compiler: ${GLSLC_EXECUTABLE} (glslc)")
set(${OUT_COMPILER} "${GLSLC_EXECUTABLE}" PARENT_SCOPE)
set(${OUT_COMPILER_TYPE} "glslc" PARENT_SCOPE)
return()
endif()
# 尝试查找 glslangValidator
find_program(GLSLANG_VALIDATOR_EXECUTABLE glslangValidator
HINTS
$ENV{VULKAN_SDK}/Bin
$ENV{VULKAN_SDK}/bin
${Vulkan_GLSLANG_VALIDATOR_EXECUTABLE}
)
if(GLSLANG_VALIDATOR_EXECUTABLE)
message(STATUS "Found GLSL compiler: ${GLSLANG_VALIDATOR_EXECUTABLE} (glslangValidator)")
set(${OUT_COMPILER} "${GLSLANG_VALIDATOR_EXECUTABLE}" PARENT_SCOPE)
set(${OUT_COMPILER_TYPE} "glslangValidator" PARENT_SCOPE)
return()
endif()
message(FATAL_ERROR
"No GLSL compiler found. Please install Vulkan SDK or shaderc.\n"
"Expected: glslc or glslangValidator in PATH or VULKAN_SDK"
)
endfunction()
# ============================================================================
# 内部变量设置
# ============================================================================
# 着色器编译器目标名称
set(MIRAI_SHADER_COMPILER_TARGET "mirai_shader_compile")
# 代码生成脚本路径
set(MIRAI_SHADER_GENERATOR_SCRIPT "${CMAKE_SOURCE_DIR}/tools/generate_shader_bindings.py")
# 模板目录
set(MIRAI_SHADER_TEMPLATE_DIR "${CMAKE_SOURCE_DIR}/tools/templates")
# ============================================================================
# _mirai_ensure_shader_compiler - 内部函数,确保着色器编译器可用
# ============================================================================
function(_mirai_ensure_shader_compiler)
# 检查编译器目标是否存在
if(NOT TARGET ${MIRAI_SHADER_COMPILER_TARGET})
message(FATAL_ERROR
"Shader compiler target '${MIRAI_SHADER_COMPILER_TARGET}' not found.\n"
"Make sure add_subdirectory(tools/shader_compile) is called before using add_glsl_shader_library()."
)
endif()
endfunction()
# ============================================================================
# _get_shader_stage_from_extension - 从文件扩展名获取着色器阶段
# ============================================================================
function(_get_shader_stage_from_extension FILE_PATH OUT_STAGE)
get_filename_component(EXT "${FILE_PATH}" EXT)
string(TOLOWER "${EXT}" EXT_LOWER)
if(EXT_LOWER STREQUAL ".vert")
set(${OUT_STAGE} "vert" PARENT_SCOPE)
elseif(EXT_LOWER STREQUAL ".frag")
set(${OUT_STAGE} "frag" PARENT_SCOPE)
elseif(EXT_LOWER STREQUAL ".comp")
set(${OUT_STAGE} "comp" PARENT_SCOPE)
elseif(EXT_LOWER STREQUAL ".geom")
set(${OUT_STAGE} "geom" PARENT_SCOPE)
elseif(EXT_LOWER STREQUAL ".tesc")
set(${OUT_STAGE} "tesc" PARENT_SCOPE)
elseif(EXT_LOWER STREQUAL ".tese")
set(${OUT_STAGE} "tese" PARENT_SCOPE)
else()
set(${OUT_STAGE} "" PARENT_SCOPE)
endif()
endfunction()
# ============================================================================
# compile_glsl_shader - 编译单个 GLSL 着色器
# ============================================================================
#
# 用法:
# compile_glsl_shader(
# INPUT shader.vert
# OUTPUT shader.vert.spv
# STAGE vert
# DEFINES DEBUG FEATURE_X
# INCLUDE_DIRS include/
# )
#
# 参数:
# INPUT - 输入 GLSL 文件路径(必需)
# OUTPUT - 输出 SPIR-V 文件路径(必需)
# STAGE - 着色器阶段 (vert/frag/comp/geom/tesc/tese)(可选,自动检测)
# DEFINES - 预处理器定义(可选)
# INCLUDE_DIRS - include 目录(可选)
#
function(compile_glsl_shader)
cmake_parse_arguments(SHADER "" "INPUT;OUTPUT;STAGE" "DEFINES;INCLUDE_DIRS" ${ARGN})
# 验证必需参数
if(NOT SHADER_INPUT)
message(FATAL_ERROR "compile_glsl_shader: INPUT is required")
endif()
if(NOT SHADER_OUTPUT)
message(FATAL_ERROR "compile_glsl_shader: OUTPUT is required")
endif()
# 自动检测着色器阶段
if(NOT SHADER_STAGE)
_get_shader_stage_from_extension("${SHADER_INPUT}" SHADER_STAGE)
if(NOT SHADER_STAGE)
message(FATAL_ERROR "compile_glsl_shader: Cannot determine shader stage from extension. Please specify STAGE.")
endif()
endif()
# 查找 GLSL 编译器
find_glsl_compiler(GLSL_COMPILER GLSL_COMPILER_TYPE)
# 构建编译命令参数
set(COMPILE_ARGS "")
if(GLSL_COMPILER_TYPE STREQUAL "glslc")
# glslc 参数
list(APPEND COMPILE_ARGS "-fshader-stage=${SHADER_STAGE}")
list(APPEND COMPILE_ARGS "--target-env=vulkan1.3")
list(APPEND COMPILE_ARGS "-o" "${SHADER_OUTPUT}")
# 添加 include 目录
foreach(INC_DIR ${SHADER_INCLUDE_DIRS})
list(APPEND COMPILE_ARGS "-I${INC_DIR}")
endforeach()
# 添加宏定义
foreach(DEF ${SHADER_DEFINES})
list(APPEND COMPILE_ARGS "-D${DEF}")
endforeach()
list(APPEND COMPILE_ARGS "${SHADER_INPUT}")
else()
# glslangValidator 参数
list(APPEND COMPILE_ARGS "-V") # 生成 SPIR-V
list(APPEND COMPILE_ARGS "--target-env" "vulkan1.3")
list(APPEND COMPILE_ARGS "-S" "${SHADER_STAGE}")
list(APPEND COMPILE_ARGS "-o" "${SHADER_OUTPUT}")
# 添加 include 目录
foreach(INC_DIR ${SHADER_INCLUDE_DIRS})
list(APPEND COMPILE_ARGS "-I${INC_DIR}")
endforeach()
# 添加宏定义
foreach(DEF ${SHADER_DEFINES})
list(APPEND COMPILE_ARGS "-D${DEF}")
endforeach()
list(APPEND COMPILE_ARGS "${SHADER_INPUT}")
endif()
# 创建编译命令
add_custom_command(
OUTPUT "${SHADER_OUTPUT}"
COMMAND ${GLSL_COMPILER} ${COMPILE_ARGS}
DEPENDS "${SHADER_INPUT}"
COMMENT "Compiling GLSL shader: ${SHADER_INPUT}"
VERBATIM
)
endfunction()
# ============================================================================
# add_glsl_shader_library - 添加 GLSL 着色器库到现有目标
# ============================================================================
#
# 将 GLSL 着色器编译和代码生成附加到现有的编译目标上
#
# 用法:
# # 方案1: 自动搜索着色器
# add_glsl_shader_library(TARGET mirai_shader
# SHADER_PATH shaders/glsl
# INCLUDE_DIRS shaders/glsl/common
# )
#
# # 方案2: 指定具体着色器文件
# add_glsl_shader_library(TARGET mirai_shader
# SHADERS shaders/ui/rect.vert shaders/ui/rect.frag
# INCLUDE_DIRS shaders/common
# )
#
# 参数:
# TARGET - 现有的编译目标(必需)
# SHADERS - 具体的着色器文件列表(可选)
# SHADER_PATH - 着色器搜索目录,相对于 CMAKE_CURRENT_SOURCE_DIR可选
# INCLUDE_DIRS - 着色器 include 路径(可选)
# DEFINES - 预处理器定义(可选)
# RECURSIVE - 是否递归搜索子目录(可选,默认 OFF
#
function(add_glsl_shader_library)
# 解析参数
set(options RECURSIVE)
set(oneValueArgs TARGET SHADER_PATH)
set(multiValueArgs SHADERS INCLUDE_DIRS DEFINES)
cmake_parse_arguments(SHADER "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# 验证必需参数
if(NOT SHADER_TARGET)
message(FATAL_ERROR "add_glsl_shader_library: TARGET is required")
endif()
# 验证目标存在
if(NOT TARGET ${SHADER_TARGET})
message(FATAL_ERROR "add_glsl_shader_library: Target '${SHADER_TARGET}' does not exist")
endif()
# 收集着色器文件
set(ALL_SHADERS "${SHADER_SHADERS}")
if(SHADER_SHADER_PATH)
set(SHADER_EXTENSIONS "vert;frag;comp;geom;tesc;tese")
if(SHADER_RECURSIVE)
foreach(EXT ${SHADER_EXTENSIONS})
file(GLOB_RECURSE FOUND_SHADERS "${CMAKE_CURRENT_SOURCE_DIR}/${SHADER_SHADER_PATH}/*.${EXT}")
list(APPEND ALL_SHADERS ${FOUND_SHADERS})
endforeach()
else()
foreach(EXT ${SHADER_EXTENSIONS})
file(GLOB FOUND_SHADERS "${CMAKE_CURRENT_SOURCE_DIR}/${SHADER_SHADER_PATH}/*.${EXT}")
list(APPEND ALL_SHADERS ${FOUND_SHADERS})
endforeach()
endif()
endif()
# 去重
if(ALL_SHADERS)
list(REMOVE_DUPLICATES ALL_SHADERS)
endif()
# 设置输出目录
set(SHADER_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
set(SHADER_INTERMEDIATE_DIR "${CMAKE_CURRENT_BINARY_DIR}/shader_intermediate")
file(MAKE_DIRECTORY "${SHADER_OUTPUT_DIR}")
file(MAKE_DIRECTORY "${SHADER_INTERMEDIATE_DIR}")
# 确保编译器可用
_mirai_ensure_shader_compiler()
# 查找 Python with Jinja2
find_python_with_jinja2(PYTHON_EXECUTABLE)
# 查找 GLSL 编译器
find_glsl_compiler(GLSL_COMPILER GLSL_COMPILER_TYPE)
# 构建 include 路径参数
set(INCLUDE_ARGS "")
foreach(INC_DIR ${SHADER_INCLUDE_DIRS})
if(IS_ABSOLUTE "${INC_DIR}")
list(APPEND INCLUDE_ARGS "${INC_DIR}")
else()
list(APPEND INCLUDE_ARGS "${CMAKE_CURRENT_SOURCE_DIR}/${INC_DIR}")
endif()
endforeach()
# 收集生成的文件
set(ALL_GENERATED_HEADERS "")
set(ALL_SPV_FILES "")
# 按着色器名称分组(去掉扩展名后相同的文件归为一组)
set(SHADER_GROUPS "")
foreach(SHADER_SOURCE ${ALL_SHADERS})
get_filename_component(SHADER_NAME_WE "${SHADER_SOURCE}" NAME_WE)
get_filename_component(SHADER_DIR "${SHADER_SOURCE}" DIRECTORY)
# 创建唯一的组标识符
string(REPLACE "/" "_" DIR_SAFE "${SHADER_DIR}")
set(GROUP_ID "${DIR_SAFE}_${SHADER_NAME_WE}")
# 添加到组列表
list(APPEND SHADER_GROUPS "${GROUP_ID}")
list(APPEND ${GROUP_ID}_SOURCES "${SHADER_SOURCE}")
set(${GROUP_ID}_NAME "${SHADER_NAME_WE}")
set(${GROUP_ID}_DIR "${SHADER_DIR}")
endforeach()
# 去重组列表
if(SHADER_GROUPS)
list(REMOVE_DUPLICATES SHADER_GROUPS)
endif()
# 处理每个着色器组
foreach(GROUP_ID ${SHADER_GROUPS})
set(GROUP_SOURCES "${${GROUP_ID}_SOURCES}")
set(GROUP_NAME "${${GROUP_ID}_NAME}")
set(GROUP_DIR "${${GROUP_ID}_DIR}")
# 每个着色器组的输出目录
set(SPIRV_OUTPUT_DIR "${SHADER_INTERMEDIATE_DIR}/${GROUP_NAME}")
set(GENERATED_HPP "${SHADER_OUTPUT_DIR}/${GROUP_NAME}_bindings.hpp")
file(MAKE_DIRECTORY "${SPIRV_OUTPUT_DIR}")
set(GROUP_SPV_FILES "")
# 编译组内的每个着色器
foreach(SHADER_SOURCE ${GROUP_SOURCES})
get_filename_component(SHADER_FILENAME "${SHADER_SOURCE}" NAME)
_get_shader_stage_from_extension("${SHADER_SOURCE}" SHADER_STAGE)
if(NOT SHADER_STAGE)
message(WARNING "Skipping unknown shader type: ${SHADER_SOURCE}")
continue()
endif()
set(SPV_OUTPUT "${SPIRV_OUTPUT_DIR}/${SHADER_FILENAME}.spv")
# 构建编译命令参数
set(COMPILE_ARGS "")
if(GLSL_COMPILER_TYPE STREQUAL "glslc")
list(APPEND COMPILE_ARGS "-fshader-stage=${SHADER_STAGE}")
list(APPEND COMPILE_ARGS "--target-env=vulkan1.3")
list(APPEND COMPILE_ARGS "-o" "${SPV_OUTPUT}")
foreach(INC_DIR ${INCLUDE_ARGS})
list(APPEND COMPILE_ARGS "-I${INC_DIR}")
endforeach()
foreach(DEF ${SHADER_DEFINES})
list(APPEND COMPILE_ARGS "-D${DEF}")
endforeach()
list(APPEND COMPILE_ARGS "${SHADER_SOURCE}")
else()
list(APPEND COMPILE_ARGS "-V")
list(APPEND COMPILE_ARGS "--target-env" "vulkan1.3")
list(APPEND COMPILE_ARGS "-S" "${SHADER_STAGE}")
list(APPEND COMPILE_ARGS "-o" "${SPV_OUTPUT}")
foreach(INC_DIR ${INCLUDE_ARGS})
list(APPEND COMPILE_ARGS "-I${INC_DIR}")
endforeach()
foreach(DEF ${SHADER_DEFINES})
list(APPEND COMPILE_ARGS "-D${DEF}")
endforeach()
list(APPEND COMPILE_ARGS "${SHADER_SOURCE}")
endif()
# 创建编译命令
add_custom_command(
OUTPUT "${SPV_OUTPUT}"
COMMAND ${GLSL_COMPILER} ${COMPILE_ARGS}
DEPENDS "${SHADER_SOURCE}"
COMMENT "Compiling GLSL: ${SHADER_FILENAME}"
VERBATIM
)
list(APPEND GROUP_SPV_FILES "${SPV_OUTPUT}")
list(APPEND ALL_SPV_FILES "${SPV_OUTPUT}")
endforeach()
# 使用 mirai_shader_compile 工具生成反射和绑定
if(GROUP_SPV_FILES)
# 创建标记文件表示编译完成
set(COMPILED_MARKER "${SPIRV_OUTPUT_DIR}/.compiled")
add_custom_command(
OUTPUT "${COMPILED_MARKER}"
COMMAND ${CMAKE_COMMAND} -E touch "${COMPILED_MARKER}"
DEPENDS ${GROUP_SPV_FILES}
COMMENT "GLSL shaders compiled: ${GROUP_NAME}"
VERBATIM
)
# 生成绑定头文件
add_custom_command(
OUTPUT "${GENERATED_HPP}"
COMMAND $<TARGET_FILE:${MIRAI_SHADER_COMPILER_TARGET}>
--dir "${SPIRV_OUTPUT_DIR}"
--output "${GENERATED_HPP}"
--name "${GROUP_NAME}"
DEPENDS "${COMPILED_MARKER}" ${MIRAI_SHADER_COMPILER_TARGET}
COMMENT "Generating bindings: ${GROUP_NAME}"
VERBATIM
)
list(APPEND ALL_GENERATED_HEADERS "${GENERATED_HPP}")
endif()
endforeach()
# 附加到目标:添加生成目录到 include 路径
target_include_directories(${SHADER_TARGET} PUBLIC
$<BUILD_INTERFACE:${SHADER_OUTPUT_DIR}>
)
# 添加编译依赖
if(ALL_GENERATED_HEADERS)
add_custom_target(${SHADER_TARGET}_shaders DEPENDS ${ALL_GENERATED_HEADERS})
add_dependencies(${SHADER_TARGET} ${SHADER_TARGET}_shaders)
endif()
# 导出变量
set(${SHADER_TARGET}_SHADER_HEADERS ${ALL_GENERATED_HEADERS} PARENT_SCOPE)
set(${SHADER_TARGET}_SHADER_OUTPUT_DIR ${SHADER_OUTPUT_DIR} PARENT_SCOPE)
set(${SHADER_TARGET}_SPV_FILES ${ALL_SPV_FILES} PARENT_SCOPE)
message(STATUS "Added GLSL shaders to target: ${SHADER_TARGET}")
message(STATUS " Output: ${SHADER_OUTPUT_DIR}")
if(ALL_SHADERS)
list(LENGTH ALL_SHADERS SHADER_COUNT)
message(STATUS " Shaders: ${SHADER_COUNT} files")
endif()
endfunction()
# ============================================================================
# add_shader_library - 兼容性别名(调用 add_glsl_shader_library
# ============================================================================
#
# 为了向后兼容,保留 add_shader_library 函数名
# 现在默认使用 GLSL 编译
#
function(add_shader_library)
# 解析参数
set(options RECURSIVE)
set(oneValueArgs TARGET SHADER_PATH)
set(multiValueArgs SHADERS REF_PATHS DEFINES)
cmake_parse_arguments(SHADER "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# 转换参数并调用新函数
set(FORWARD_ARGS "TARGET" "${SHADER_TARGET}")
if(SHADER_SHADER_PATH)
list(APPEND FORWARD_ARGS "SHADER_PATH" "${SHADER_SHADER_PATH}")
endif()
if(SHADER_SHADERS)
list(APPEND FORWARD_ARGS "SHADERS" ${SHADER_SHADERS})
endif()
if(SHADER_REF_PATHS)
list(APPEND FORWARD_ARGS "INCLUDE_DIRS" ${SHADER_REF_PATHS})
endif()
if(SHADER_DEFINES)
list(APPEND FORWARD_ARGS "DEFINES" ${SHADER_DEFINES})
endif()
if(SHADER_RECURSIVE)
list(APPEND FORWARD_ARGS "RECURSIVE")
endif()
add_glsl_shader_library(${FORWARD_ARGS})
endfunction()

View File

@@ -1,15 +1,22 @@
---
version: 0.1.0
status: accepted
status: superseded
created: 2024-01-15
superseded_by: adr-003-glsl-shader-system.md
author: MIRAI Team
---
# ADR-002: 选择 Slang 作为着色器语言
> **⚠️ 已废弃**:本 ADR 已被 [ADR-003: 迁移到 GLSL 着色器系统](adr-003-glsl-shader-system.md) 取代。
>
> 项目已从 Slang 迁移到标准 GLSL以简化工具链并提供更好的用户自定义支持。
## 状态
**已接受** - 2024-01-15
**已废弃** - 2024-01-20被 [ADR-003](adr-003-glsl-shader-system.md) 取代)
原状态:已接受 - 2024-01-15
## 背景

View File

@@ -0,0 +1,192 @@
---
version: 0.2.0
status: accepted
created: 2024-01-20
author: MIRAI Team
supersedes: adr-002-slang-shader-system.md
---
# ADR-003: 迁移到 GLSL 着色器系统
## 状态
**已接受** - 2024-01-20
## 背景
原系统使用 Slang 着色器语言(参见 [ADR-002](adr-002-slang-shader-system.md)),虽然 Slang 提供了强大的反射和泛型功能,但在实际使用中遇到了以下问题:
1. **工具链复杂性**Slang 编译器需要额外的依赖和配置
2. **用户学习成本**:开发者需要学习新的着色器语言
3. **调试困难**Slang 生成的 SPIR-V 难以调试
4. **生态系统兼容性**:大多数着色器工具和示例都基于 GLSL
## 决策
迁移到标准 **GLSL** 着色器语言,采用以下架构设计:
### 1. 资源分配约定
| 资源类型 | 分配 | 说明 |
|---------|------|------|
| **Set 0** | 框架专用 | 用户不要使用 |
| **Set 1** | 用户 UBO | 用户自定义 Uniform Buffer |
| **Set 2** | 用户纹理 | 用户自定义纹理 |
| **Push Constant** | 用户专用 | 最大 128 bytes |
### 2. 框架 UBO 结构 (Set 0, Binding 0)
```glsl
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds; // (x, y, width, height)
float u_opacity; // 0.0 - 1.0
float _pad0;
float _pad1;
float _pad2;
} widget;
```
### 3. 四种渲染模式
| 模式 | 顶点着色器 | 片段着色器 | 说明 |
|------|-----------|-----------|------|
| **Backdrop** | 框架提供 | 用户提供 | 后处理效果,框架提供背景纹理 |
| **Procedural** | 框架提供 | 用户提供 | 程序化生成 |
| **Mask** | 框架提供 | 用户提供 | 遮罩效果 |
| **Custom Mesh** | 用户提供 | 用户提供 | 自定义网格渲染 |
### 4. Push Constant 完全留给用户
框架不使用 Push Constant用户可以完全控制这 128 bytes 的快速更新数据。
## 理由
### 1. 简化工具链
```
原流程: .slang → slangc → SPIR-V + 反射 JSON → C++ 绑定
新流程: .glsl → glslangValidator → SPIR-V
```
### 2. 用户友好
- GLSL 是业界标准,大多数图形开发者已经熟悉
- 丰富的学习资源和示例代码
- 更好的 IDE 支持和语法高亮
### 3. 调试便利
- SPIR-V 工具链成熟
- RenderDoc 等工具原生支持
- 错误信息更易理解
### 4. 灵活的用户控制
- Push Constant 完全留给用户,支持高频参数更新
- 用户可以自由定义 Set 1/2 的资源布局
- 模板文件提供清晰的起点
## 替代方案
| 方案 | 优点 | 缺点 |
|------|------|------|
| **GLSL** (选择) | 标准、简单、广泛支持 | 无反射、手动绑定 |
| Slang | 反射、泛型、多后端 | 工具链复杂、学习成本 |
| HLSL | DX12 原生 | 仅限 Windows |
| WGSL | WebGPU 原生 | 生态不成熟 |
## 后果
### 积极
- 简化了编译工具链
- 降低了用户学习成本
- 用户可以完全控制 Push Constant
- 更好的调试体验
- 更广泛的社区支持
### 消极
- 失去了 Slang 的自动反射功能
- 需要用户手动复制着色器模板
- 需要手动维护 C++ 和 GLSL 的数据结构同步
## 实现
### 着色器编译流程
```
用户 GLSL 片段着色器
glslangValidator -V
SPIR-V
spirv-cross (可选反射)
```
### 目录结构
```
src/shader/shaders/glsl/
├── framework/ # 框架内置着色器
│ ├── quad.vert # 四边形顶点着色器
│ └── fullscreen.vert # 全屏顶点着色器
├── templates/ # 用户模板
│ ├── procedural.frag.template
│ ├── backdrop.frag.template
│ ├── mask.frag.template
│ └── mesh.vert.template
└── examples/ # 示例着色器
├── gradient.frag
├── blur.frag
└── rounded_rect.frag
```
### CMake 集成
```cmake
function(compile_glsl_shader INPUT OUTPUT)
add_custom_command(
OUTPUT ${OUTPUT}
COMMAND glslangValidator -V ${INPUT} -o ${OUTPUT}
DEPENDS ${INPUT}
COMMENT "Compiling ${INPUT}"
)
endfunction()
```
### C++ 端 UBO 结构
```cpp
namespace mirai {
struct alignas(16) widget_bounds_ubo {
float bounds[4]; // x, y, width, height
float opacity;
float _pad[3];
};
static_assert(sizeof(widget_bounds_ubo) == 32);
} // namespace mirai
```
## 迁移指南
### 从 Slang 迁移到 GLSL
1. **复制模板文件**:根据渲染模式选择对应的模板
2. **转换语法**
- `float4``vec4`
- `float2``vec2`
- `ConstantBuffer<T>``uniform T { ... }`
- `[[vk::binding(n, m)]]``layout(set = m, binding = n)`
3. **更新绑定**:确保使用正确的 Set 和 Binding
4. **测试验证**:使用 glslangValidator 验证语法
## 参考
- [GLSL 规范](https://www.khronos.org/opengl/wiki/Core_Language_(GLSL))
- [Vulkan GLSL](https://www.khronos.org/opengl/wiki/Vulkan_GLSL)
- [glslangValidator](https://github.com/KhronosGroup/glslang)
- [MIRAI GLSL 着色器系统设计](../../plans/glsl_shader_system_design.md)

1002
docs/demo_architecture.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,646 @@
# MIRAI Shader Widget 统一接口架构设计
## 1. 概述
本文档描述 MIRAI 框架的着色器控件Shader Widget统一接口架构设计。该架构基于标准 GLSL 着色器语言,支持四种渲染模式,每种模式提供特定的核心功能,而具体效果参数由派生类和着色器定义。
> **注意**:本文档反映了从 Slang 迁移到 GLSL 后的新架构。详见 [ADR-003: 迁移到 GLSL 着色器系统](adr/adr-003-glsl-shader-system.md)。
### 1.1 设计目标
1. **使用标准 GLSL**:采用业界标准着色器语言,降低学习成本
2. **Push Constant 完全留给用户**:框架不使用 Push Constant用户可完全控制
3. **框架数据最小化**:只提供 bounds 和 opacity
4. **简单约定**:提前约定框架和用户各自使用的资源
5. **模板驱动**:提供模板让用户复制和修改
### 1.2 四种渲染模式
| 模式 | 顶点着色器 | 片段着色器 | 说明 |
|------|-----------|-----------|------|
| **Backdrop** | 框架提供 | 用户提供 | 后处理效果,框架提供背景纹理 |
| **Procedural** | 框架提供 | 用户提供 | 程序化生成 |
| **Mask** | 框架提供 | 用户提供 | 遮罩效果 |
| **Custom Mesh** | 用户提供 | 用户提供 | 自定义网格渲染 |
---
## 2. 资源分配约定
### 2.1 Descriptor Set 布局
```
┌─────────────────────────────────────────────────────────────┐
│ 资源分配约定表 │
├─────────────────┬───────────────────────────────────────────┤
│ 资源类型 │ 分配 │
├─────────────────┼───────────────────────────────────────────┤
│ Set 0 │ 框架专用(用户不要使用) │
│ Set 1 │ 用户 UBO │
│ Set 2 │ 用户纹理 │
│ Push Constant │ 用户专用(最大 128 bytes
├─────────────────┼───────────────────────────────────────────┤
│ 顶点着色器 │ 框架提供Custom Mesh 除外) │
│ 片段着色器 │ 用户提供 │
└─────────────────┴───────────────────────────────────────────┘
```
### 2.2 Set 0框架专用
#### 通用模式Procedural, Mask
```glsl
// Set 0: 框架专用 - 用户不要在 Set 0 定义任何资源
// Binding 0: Widget 边界
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds; // (x, y, width, height)
float u_opacity; // 0.0 - 1.0
float _pad0;
float _pad1;
float _pad2;
} widget;
// 大小: 32 bytes
```
#### Backdrop 模式
```glsl
// Binding 0: Widget 边界(同上)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0;
float _pad1;
float _pad2;
} widget;
// Binding 1: 背景纹理(框架自动绑定)
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
```
### 2.3 Set 1用户 UBO
用户可以在 Set 1 定义自己的 Uniform Buffer
```glsl
// 示例:模糊参数
layout(set = 1, binding = 0, std140) uniform BlurParams {
float sigma; // 高斯分布标准差
int samples; // 采样半径
vec2 texel_size; // 纹理像素大小
} params;
```
### 2.4 Set 2用户纹理
用户可以在 Set 2 定义自己的纹理:
```glsl
// 示例:自定义纹理
layout(set = 2, binding = 0) uniform sampler2D u_my_texture;
layout(set = 2, binding = 1) uniform sampler2D u_noise_texture;
```
### 2.5 Push Constant用户专用
Push Constant 完全留给用户,最大 128 bytes
```glsl
layout(push_constant) uniform PushConstants {
vec4 color_start; // 起始颜色
vec4 color_end; // 结束颜色
float angle; // 渐变角度
float _pad[3];
} pc;
```
---
## 3. 框架内置顶点着色器
框架为 Backdrop/Procedural/Mask 模式提供内置顶点着色器,用户无需编写。
### 3.1 顶点着色器输出
框架的顶点着色器会输出以下数据给片段着色器:
```glsl
// 框架顶点着色器输出(用户片段着色器的输入)
layout(location = 0) in vec2 v_uv; // UV 坐标 (0-1)
layout(location = 1) in vec2 v_local_pos; // 局部坐标 (0-1),相对于 widget bounds
```
### 3.2 框架顶点着色器实现
```glsl
// 框架内部实现 - quad.vert
#version 450
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_uv;
layout(location = 0) out vec2 v_uv;
layout(location = 1) out vec2 v_local_pos;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0;
float _pad1;
float _pad2;
} widget;
void main() {
// 将顶点位置转换到 NDC
vec2 pos = a_position * widget.u_bounds.zw + widget.u_bounds.xy;
vec2 ndc = pos * 2.0 - 1.0;
gl_Position = vec4(ndc, 0.0, 1.0);
v_uv = a_uv;
v_local_pos = a_position;
}
```
---
## 4. 用户片段着色器模板
### 4.1 Procedural 模式模板
用于程序化生成效果(渐变、噪声、图案等):
```glsl
#version 450
// ============================================================
// 框架输入(不要修改)
// ============================================================
layout(location = 0) in vec2 v_uv; // UV 坐标 (0-1)
layout(location = 1) in vec2 v_local_pos; // 局部坐标 (0-1)
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用(不要修改)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds; // (x, y, width, height)
float u_opacity; // 0.0 - 1.0
float _pad0;
float _pad1;
float _pad2;
} widget;
// ============================================================
// 用户区域(自由定义)
// ============================================================
// Set 1: 用户 UBO可选
// layout(set = 1, binding = 0, std140) uniform MyParams {
// float time;
// // ... 自定义参数
// } params;
// Set 2: 用户纹理(可选)
// layout(set = 2, binding = 0) uniform sampler2D u_my_texture;
// Push Constant可选最大 128 bytes
layout(push_constant) uniform PushConstants {
vec4 color;
// ... 自定义参数
} pc;
// ============================================================
// 主函数
// ============================================================
void main() {
// TODO: 用户实现程序化效果
frag_color = pc.color;
frag_color.a *= widget.u_opacity;
}
```
### 4.2 Backdrop 模式模板
用于后处理效果(模糊、颜色调整等):
```glsl
#version 450
// ============================================================
// 框架输入(不要修改)
// ============================================================
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用(不要修改)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0;
float _pad1;
float _pad2;
} widget;
// 背景纹理(框架提供)
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
// ============================================================
// 用户区域(自由定义)
// ============================================================
// Set 1: 用户 UBO可选
// layout(set = 1, binding = 0, std140) uniform MyParams {
// float radius;
// // ... 自定义参数
// } params;
// Push Constant可选
// layout(push_constant) uniform PushConstants {
// // ... 自定义参数
// } pc;
// ============================================================
// 主函数
// ============================================================
void main() {
// 采样背景纹理
vec4 color = texture(u_backdrop, v_uv);
// TODO: 用户实现后处理效果
frag_color = color;
frag_color.a *= widget.u_opacity;
}
```
### 4.3 Mask 模式模板
用于遮罩效果(圆角矩形、圆形、自定义形状等):
```glsl
#version 450
// ============================================================
// 框架输入(不要修改)
// ============================================================
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用(不要修改)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0;
float _pad1;
float _pad2;
} widget;
// ============================================================
// 用户区域(自由定义)
// ============================================================
// Push Constant可选
layout(push_constant) uniform PushConstants {
vec4 color; // 遮罩颜色
float corner_radius; // 圆角半径
float _pad[3];
} pc;
// ============================================================
// 主函数
// ============================================================
void main() {
// 示例:圆角矩形遮罩
vec2 size = widget.u_bounds.zw;
vec2 half_size = size * 0.5;
vec2 p = (v_local_pos - 0.5) * size;
vec2 q = abs(p) - half_size + pc.corner_radius;
float d = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - pc.corner_radius;
float mask = 1.0 - smoothstep(-1.0, 1.0, d);
frag_color = pc.color;
frag_color.a *= mask * widget.u_opacity;
}
```
### 4.4 Custom Mesh 模式模板
用于自定义网格渲染(粒子系统、自定义形状等):
**顶点着色器模板:**
```glsl
#version 450
// ============================================================
// 用户定义顶点输入
// ============================================================
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec2 a_uv;
layout(location = 2) in vec4 a_color;
layout(location = 0) out vec2 v_uv;
layout(location = 1) out vec4 v_color;
// Set 0: 框架专用(不要修改)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0;
float _pad1;
float _pad2;
} widget;
// ============================================================
// 用户区域(自由定义)
// ============================================================
// Push Constant
layout(push_constant) uniform PushConstants {
mat4 mvp;
} pc;
// ============================================================
// 主函数
// ============================================================
void main() {
gl_Position = pc.mvp * vec4(a_position, 1.0);
v_uv = a_uv;
v_color = a_color;
}
```
**片段着色器模板:**
```glsl
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec4 v_color;
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用(不要修改)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0;
float _pad1;
float _pad2;
} widget;
// ============================================================
// 用户区域(自由定义)
// ============================================================
// Set 2: 用户纹理(可选)
layout(set = 2, binding = 0) uniform sampler2D u_texture;
void main() {
frag_color = texture(u_texture, v_uv) * v_color;
frag_color.a *= widget.u_opacity;
}
```
---
## 5. 编译系统
### 5.1 编译流程
```
用户 GLSL 着色器
glslangValidator -V
SPIR-V
spirv-cross (可选反射)
```
### 5.2 CMake 集成
```cmake
# cmake/shader_compile.cmake
function(compile_glsl_shader INPUT OUTPUT)
get_filename_component(INPUT_NAME ${INPUT} NAME)
add_custom_command(
OUTPUT ${OUTPUT}
COMMAND glslangValidator -V ${INPUT} -o ${OUTPUT}
DEPENDS ${INPUT}
COMMENT "Compiling GLSL shader: ${INPUT_NAME}"
VERBATIM
)
endfunction()
# 批量编译着色器
function(compile_glsl_shaders TARGET_NAME SHADER_DIR OUTPUT_DIR)
file(GLOB_RECURSE SHADER_FILES
"${SHADER_DIR}/*.vert"
"${SHADER_DIR}/*.frag"
"${SHADER_DIR}/*.comp"
)
set(SPIRV_FILES)
foreach(SHADER ${SHADER_FILES})
get_filename_component(SHADER_NAME ${SHADER} NAME)
set(SPIRV_FILE "${OUTPUT_DIR}/${SHADER_NAME}.spv")
compile_glsl_shader(${SHADER} ${SPIRV_FILE})
list(APPEND SPIRV_FILES ${SPIRV_FILE})
endforeach()
add_custom_target(${TARGET_NAME} DEPENDS ${SPIRV_FILES})
endfunction()
```
### 5.3 目录结构
```
src/shader/shaders/glsl/
├── framework/ # 框架内置着色器
│ ├── quad.vert # 四边形顶点着色器
│ └── fullscreen.vert # 全屏顶点着色器
├── templates/ # 用户模板(复制使用)
│ ├── procedural.frag.template
│ ├── backdrop.frag.template
│ ├── mask.frag.template
│ └── mesh.vert.template
└── examples/ # 示例着色器
├── gradient.frag # 渐变效果
├── blur.frag # 模糊效果
└── rounded_rect.frag # 圆角矩形
```
---
## 6. C++ 端实现
### 6.1 框架 UBO 结构
```cpp
namespace mirai {
struct alignas(16) widget_bounds_ubo {
float bounds[4]; // x, y, width, height
float opacity;
float _pad[3];
};
static_assert(sizeof(widget_bounds_ubo) == 32);
} // namespace mirai
```
### 6.2 描述符集布局创建
```cpp
namespace mirai {
// 通用模式 Set 0 布局
inline vk::UniqueDescriptorSetLayout create_set0_layout_common(vk::Device device) {
vk::DescriptorSetLayoutBinding binding{
0, vk::DescriptorType::eUniformBuffer, 1,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment
};
return device.createDescriptorSetLayoutUnique({{}, 1, &binding});
}
// Backdrop 模式 Set 0 布局
inline vk::UniqueDescriptorSetLayout create_set0_layout_backdrop(vk::Device device) {
std::array<vk::DescriptorSetLayoutBinding, 2> bindings = {{
{0, vk::DescriptorType::eUniformBuffer, 1,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
{1, vk::DescriptorType::eCombinedImageSampler, 1,
vk::ShaderStageFlagBits::eFragment}
}};
return device.createDescriptorSetLayoutUnique({{}, bindings});
}
} // namespace mirai
```
### 6.3 Push Constant 范围
```cpp
// 用户 Push Constant 范围128 bytes
vk::PushConstantRange user_push_constant_range{
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
0, // offset
128 // size (最大)
};
```
---
## 7. 整体架构图
### 7.1 系统架构总览
```mermaid
flowchart TB
subgraph BuildTime[构建时]
A[GLSL 源文件] --> B[glslangValidator]
B --> C[SPIR-V 字节码]
end
subgraph Runtime[运行时]
C --> D[shader_library]
D --> E[shader_module]
subgraph ShaderWidgets[Shader Widget 层]
F[shader_widget 基类]
G[backdrop_widget]
H[procedural_widget]
I[mask_widget]
J[custom_mesh_widget]
F --> G
F --> H
F --> I
F --> J
end
E --> F
subgraph Resources[资源管理]
K[gpu_allocator]
L[descriptor_pool]
end
F --> K
F --> L
subgraph Rendering[渲染管线]
M[pipeline]
N[command_buffer]
end
F --> M
M --> N
end
```
### 7.2 数据流
```mermaid
sequenceDiagram
participant User as 用户代码
participant Widget as shader_widget
participant UBO as Uniform Buffer
participant PC as Push Constant
participant GPU as GPU
User->>Widget: 设置参数
Widget->>UBO: 更新 Set 1 UBO
Widget->>PC: 更新 Push Constant
loop 每帧渲染
Widget->>GPU: vkCmdBindPipeline
Widget->>GPU: vkCmdBindDescriptorSets (Set 0, 1, 2)
Widget->>GPU: vkCmdPushConstants
Widget->>GPU: vkCmdDraw
end
```
---
## 8. 资源分配总结
### 8.1 资源归属表
| 资源 | 归属 | 说明 |
|------|------|------|
| Set 0 | 框架 | WidgetBounds + Backdrop纹理 |
| Set 1 | 用户 | 用户 UBO |
| Set 2 | 用户 | 用户纹理 |
| Push Constant | 用户 | 最大 128 bytes |
| 顶点着色器 | 框架 | Custom Mesh 除外 |
| 片段着色器 | 用户 | 所有模式 |
### 8.2 框架提供的数据
| 数据 | 位置 | 类型 | 说明 |
|------|------|------|------|
| u_bounds | Set 0, Binding 0 | vec4 | Widget 边界 (x, y, w, h) |
| u_opacity | Set 0, Binding 0 | float | 透明度 (0-1) |
| u_backdrop | Set 0, Binding 1 | sampler2D | 背景纹理(仅 Backdrop 模式) |
### 8.3 框架顶点着色器输出
| 输出 | location | 类型 | 说明 |
|------|----------|------|------|
| v_uv | 0 | vec2 | UV 坐标 (0-1) |
| v_local_pos | 1 | vec2 | 局部坐标 (0-1) |
---
## 9. 相关文档
- [ADR-003: 迁移到 GLSL 着色器系统](adr/adr-003-glsl-shader-system.md) - 架构决策记录
- [GLSL 着色器系统设计](../plans/glsl_shader_system_design.md) - 详细设计文档
- [Shader Widget 使用示例](shader_widget_examples.md) - 使用示例

View File

@@ -0,0 +1,842 @@
# MIRAI Shader Widget 使用示例
本文档提供 MIRAI 框架着色器控件系统的详细使用示例,涵盖四种渲染模式和自定义控件开发。
> **注意**:本文档使用标准 GLSL 着色器语言。详见 [ADR-003: 迁移到 GLSL 着色器系统](adr/adr-003-glsl-shader-system.md)。
## 目录
1. [Procedural 模式示例](#1-procedural-模式示例)
2. [Backdrop 模式示例](#2-backdrop-模式示例)
3. [Mask 模式示例](#3-mask-模式示例)
4. [Custom Mesh 模式示例](#4-custom-mesh-模式示例)
5. [Push Constant 使用示例](#5-push-constant-使用示例)
6. [用户 UBO 使用示例](#6-用户-ubo-使用示例)
---
## 1. Procedural 模式示例
Procedural 模式用于程序化渲染,基于坐标生成图形。框架提供顶点着色器,用户只需编写片段着色器。
### 1.1 线性渐变效果
使用 Push Constant 传递颜色和角度参数:
```glsl
#version 450
// ============================================================
// 线性渐变效果 (Procedural 模式)
// ============================================================
// 框架输入
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
// Push Constant: 渐变参数
layout(push_constant) uniform PushConstants {
vec4 color_start; // 起始颜色
vec4 color_end; // 结束颜色
float angle; // 渐变角度(弧度)
float _pad[3];
} pc;
void main() {
// 计算渐变方向向量
vec2 dir = vec2(cos(pc.angle), sin(pc.angle));
// 计算当前位置在渐变方向上的投影
float t = dot(v_local_pos - 0.5, dir) + 0.5;
// 线性插值颜色
frag_color = mix(pc.color_start, pc.color_end, clamp(t, 0.0, 1.0));
// 应用框架透明度
frag_color.a *= widget.u_opacity;
}
```
### 1.2 径向渐变效果
```glsl
#version 450
// 框架输入
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
// Push Constant: 径向渐变参数
layout(push_constant) uniform PushConstants {
vec4 color_center; // 中心颜色
vec4 color_edge; // 边缘颜色
vec2 center; // 渐变中心 (0-1)
float radius; // 渐变半径
float _pad;
} pc;
void main() {
// 计算到中心的距离
float dist = length(v_local_pos - pc.center) / pc.radius;
// 径向插值
frag_color = mix(pc.color_center, pc.color_edge, clamp(dist, 0.0, 1.0));
frag_color.a *= widget.u_opacity;
}
```
### 1.3 棋盘格图案
```glsl
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
layout(push_constant) uniform PushConstants {
vec4 color_a; // 颜色 A
vec4 color_b; // 颜色 B
float grid_size; // 格子大小
float _pad[3];
} pc;
void main() {
// 计算格子坐标
vec2 grid = floor(v_local_pos * widget.u_bounds.zw / pc.grid_size);
// 棋盘格模式
float checker = mod(grid.x + grid.y, 2.0);
frag_color = mix(pc.color_a, pc.color_b, checker);
frag_color.a *= widget.u_opacity;
}
```
---
## 2. Backdrop 模式示例
Backdrop 模式用于后处理效果,框架提供背景纹理 `u_backdrop`
### 2.1 高斯模糊效果
使用用户 UBO (Set 1) 传递模糊参数:
```glsl
#version 450
// ============================================================
// 高斯模糊效果 (Backdrop 模式)
// ============================================================
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
// 背景纹理(框架提供)
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
// Set 1: 用户 UBO - 模糊参数
layout(set = 1, binding = 0, std140) uniform BlurParams {
float sigma; // 高斯分布标准差
int samples; // 采样半径
vec2 texel_size; // 纹理像素大小 (1.0/width, 1.0/height)
} params;
void main() {
vec4 color = vec4(0.0);
float total_weight = 0.0;
// 高斯模糊核
float sigma2 = params.sigma * params.sigma;
float inv_2sigma2 = 1.0 / (2.0 * sigma2);
// 双重循环采样
for (int i = -params.samples; i <= params.samples; i++) {
for (int j = -params.samples; j <= params.samples; j++) {
vec2 offset = vec2(float(i), float(j)) * params.texel_size;
// 计算高斯权重
float dist2 = float(i * i + j * j);
float weight = exp(-dist2 * inv_2sigma2);
color += texture(u_backdrop, v_uv + offset) * weight;
total_weight += weight;
}
}
frag_color = color / total_weight;
frag_color.a *= widget.u_opacity;
}
```
### 2.2 色彩调整效果
```glsl
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
// Push Constant: 色彩调整参数
layout(push_constant) uniform PushConstants {
float brightness; // 亮度 (-1 to 1)
float contrast; // 对比度 (0 to 2)
float saturation; // 饱和度 (0 to 2)
float _pad;
} pc;
void main() {
vec4 color = texture(u_backdrop, v_uv);
// 亮度调整
color.rgb += pc.brightness;
// 对比度调整
color.rgb = (color.rgb - 0.5) * pc.contrast + 0.5;
// 饱和度调整
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
color.rgb = mix(vec3(gray), color.rgb, pc.saturation);
frag_color = color;
frag_color.a *= widget.u_opacity;
}
```
### 2.3 毛玻璃效果
```glsl
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
// Set 1: 模糊参数
layout(set = 1, binding = 0, std140) uniform BlurParams {
float sigma;
int samples;
vec2 texel_size;
} blur;
// Push Constant: 毛玻璃参数
layout(push_constant) uniform PushConstants {
vec4 tint_color; // 着色颜色
float tint_amount; // 着色强度 (0-1)
float _pad[3];
} pc;
void main() {
// 模糊采样
vec4 color = vec4(0.0);
float total = 0.0;
float inv_2sigma2 = 1.0 / (2.0 * blur.sigma * blur.sigma);
for (int i = -blur.samples; i <= blur.samples; i++) {
for (int j = -blur.samples; j <= blur.samples; j++) {
vec2 offset = vec2(float(i), float(j)) * blur.texel_size;
float w = exp(-float(i*i + j*j) * inv_2sigma2);
color += texture(u_backdrop, v_uv + offset) * w;
total += w;
}
}
color /= total;
// 应用着色
color.rgb = mix(color.rgb, pc.tint_color.rgb, pc.tint_amount);
frag_color = color;
frag_color.a *= widget.u_opacity;
}
```
---
## 3. Mask 模式示例
Mask 模式用于创建遮罩效果,通常输出带有 alpha 通道的颜色。
### 3.1 圆角矩形遮罩
使用 SDF有符号距离场生成高质量圆角矩形
```glsl
#version 450
// ============================================================
// 圆角矩形遮罩 (Mask 模式)
// ============================================================
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
// Push Constant: 遮罩参数
layout(push_constant) uniform PushConstants {
vec4 color; // 遮罩颜色
float radius; // 圆角半径(像素)
float _pad[3];
} pc;
// 圆角矩形 SDF
float rounded_rect_sdf(vec2 p, vec2 half_size, float radius) {
vec2 q = abs(p) - half_size + radius;
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
}
void main() {
vec2 size = widget.u_bounds.zw;
vec2 half_size = size * 0.5;
// 转换到以中心为原点的坐标系
vec2 p = (v_local_pos - 0.5) * size;
// 计算 SDF
float d = rounded_rect_sdf(p, half_size, pc.radius);
// 抗锯齿
float alpha = 1.0 - smoothstep(-1.0, 1.0, d);
frag_color = pc.color;
frag_color.a *= alpha * widget.u_opacity;
}
```
### 3.2 圆形遮罩
```glsl
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
layout(push_constant) uniform PushConstants {
vec4 color;
vec2 center; // 圆心 (0-1)
float radius; // 半径 (相对于短边)
float softness; // 边缘柔和度
} pc;
void main() {
// 计算宽高比
vec2 size = widget.u_bounds.zw;
float aspect = size.x / size.y;
// 调整坐标以保持圆形
vec2 uv = v_local_pos;
uv.x *= aspect;
vec2 c = pc.center;
c.x *= aspect;
// 计算到圆心的距离
float dist = length(uv - c);
// 计算遮罩
float min_size = min(size.x, size.y);
float r = pc.radius * min_size / size.x; // 调整半径
float alpha = 1.0 - smoothstep(r - pc.softness, r + pc.softness, dist);
frag_color = pc.color;
frag_color.a *= alpha * widget.u_opacity;
}
```
### 3.3 四角独立圆角
```glsl
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
layout(push_constant) uniform PushConstants {
vec4 color;
vec4 radii; // (左上, 右上, 右下, 左下)
} pc;
// 四角独立圆角 SDF
float rounded_rect_sdf_4(vec2 p, vec2 half_size, vec4 radii) {
// 根据象限选择圆角半径
float r = (p.x > 0.0)
? ((p.y > 0.0) ? radii.y : radii.z) // 右上 / 右下
: ((p.y > 0.0) ? radii.x : radii.w); // 左上 / 左下
vec2 q = abs(p) - half_size + r;
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
}
void main() {
vec2 size = widget.u_bounds.zw;
vec2 half_size = size * 0.5;
vec2 p = (v_local_pos - 0.5) * size;
float d = rounded_rect_sdf_4(p, half_size, pc.radii);
float alpha = 1.0 - smoothstep(-1.0, 1.0, d);
frag_color = pc.color;
frag_color.a *= alpha * widget.u_opacity;
}
```
---
## 4. Custom Mesh 模式示例
Custom Mesh 模式需要用户同时提供顶点和片段着色器。
### 4.1 粒子系统
**顶点着色器:**
```glsl
#version 450
// 粒子实例数据
layout(location = 0) in vec3 a_position; // 粒子位置
layout(location = 1) in float a_size; // 粒子大小
layout(location = 2) in vec4 a_color; // 粒子颜色
layout(location = 3) in float a_rotation; // 粒子旋转
layout(location = 0) out vec2 v_uv;
layout(location = 1) out vec4 v_color;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
// Push Constant: 变换矩阵
layout(push_constant) uniform PushConstants {
mat4 view_proj;
} pc;
// 四边形顶点Billboard
const vec2 QUAD_VERTICES[6] = vec2[](
vec2(-0.5, -0.5), vec2(0.5, -0.5), vec2(0.5, 0.5),
vec2(-0.5, -0.5), vec2(0.5, 0.5), vec2(-0.5, 0.5)
);
const vec2 QUAD_UVS[6] = vec2[](
vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(1.0, 1.0),
vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 1.0)
);
void main() {
// 获取四边形顶点
vec2 quad_pos = QUAD_VERTICES[gl_VertexIndex % 6];
v_uv = QUAD_UVS[gl_VertexIndex % 6];
// 应用旋转
float c = cos(a_rotation);
float s = sin(a_rotation);
mat2 rot = mat2(c, -s, s, c);
quad_pos = rot * quad_pos;
// 应用大小
quad_pos *= a_size;
// 计算世界位置
vec3 world_pos = a_position + vec3(quad_pos, 0.0);
gl_Position = pc.view_proj * vec4(world_pos, 1.0);
v_color = a_color;
}
```
**片段着色器:**
```glsl
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec4 v_color;
layout(location = 0) out vec4 frag_color;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
// Set 2: 粒子纹理(可选)
layout(set = 2, binding = 0) uniform sampler2D u_particle_texture;
void main() {
// 采样粒子纹理
vec4 tex_color = texture(u_particle_texture, v_uv);
frag_color = tex_color * v_color;
frag_color.a *= widget.u_opacity;
}
```
### 4.2 简单圆形粒子(无纹理)
**片段着色器:**
```glsl
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec4 v_color;
layout(location = 0) out vec4 frag_color;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
void main() {
// 计算到中心的距离
vec2 center = v_uv - 0.5;
float dist = length(center) * 2.0;
// 圆形遮罩,带软边缘
float alpha = 1.0 - smoothstep(0.8, 1.0, dist);
frag_color = v_color;
frag_color.a *= alpha * widget.u_opacity;
}
```
---
## 5. Push Constant 使用示例
Push Constant 完全留给用户,最大 128 bytes适合高频更新的参数。
### 5.1 Push Constant 结构设计
```glsl
// 示例 1简单颜色参数 (32 bytes)
layout(push_constant) uniform PushConstants {
vec4 color; // 16 bytes
vec4 secondary; // 16 bytes
} pc;
// 示例 2渐变参数 (48 bytes)
layout(push_constant) uniform PushConstants {
vec4 color_start; // 16 bytes
vec4 color_end; // 16 bytes
float angle; // 4 bytes
float _pad[3]; // 12 bytes (对齐)
} pc;
// 示例 3变换矩阵 (64 bytes)
layout(push_constant) uniform PushConstants {
mat4 transform; // 64 bytes
} pc;
// 示例 4复杂参数 (128 bytes - 最大)
layout(push_constant) uniform PushConstants {
mat4 transform; // 64 bytes
vec4 color; // 16 bytes
vec4 params; // 16 bytes
vec4 extra[2]; // 32 bytes
} pc;
```
### 5.2 C++ 端 Push Constant 更新
```cpp
// 定义与着色器匹配的结构体
struct GradientPushConstants {
float color_start[4];
float color_end[4];
float angle;
float _pad[3];
};
static_assert(sizeof(GradientPushConstants) == 48);
// 更新 Push Constant
void update_gradient(vk::CommandBuffer cmd, vk::PipelineLayout layout) {
GradientPushConstants pc{};
pc.color_start[0] = 1.0f; pc.color_start[1] = 0.0f;
pc.color_start[2] = 0.0f; pc.color_start[3] = 1.0f;
pc.color_end[0] = 0.0f; pc.color_end[1] = 0.0f;
pc.color_end[2] = 1.0f; pc.color_end[3] = 1.0f;
pc.angle = 0.785f; // 45 度
cmd.pushConstants(
layout,
vk::ShaderStageFlagBits::eFragment,
0,
sizeof(pc),
&pc
);
}
```
---
## 6. 用户 UBO 使用示例
用户 UBO 使用 Set 1适合不频繁更新的参数。
### 6.1 UBO 结构设计
```glsl
// Set 1: 用户 UBO
layout(set = 1, binding = 0, std140) uniform MyParams {
// 注意 std140 布局规则:
// - vec4 对齐到 16 bytes
// - vec3 对齐到 16 bytes
// - vec2 对齐到 8 bytes
// - float/int 对齐到 4 bytes
// - 数组元素对齐到 16 bytes
vec4 color; // offset: 0, size: 16
vec2 scale; // offset: 16, size: 8
float intensity; // offset: 24, size: 4
float _pad0; // offset: 28, size: 4 (对齐)
vec4 extra_params; // offset: 32, size: 16
} params;
// 总大小: 48 bytes
```
### 6.2 C++ 端 UBO 结构
```cpp
// 必须与着色器中的布局完全匹配
struct alignas(16) MyParamsUBO {
float color[4]; // vec4
float scale[2]; // vec2
float intensity; // float
float _pad0; // padding
float extra_params[4]; // vec4
};
static_assert(sizeof(MyParamsUBO) == 48);
// 更新 UBO
void update_params(buffer& ubo) {
MyParamsUBO data{};
data.color[0] = 1.0f;
data.color[1] = 0.5f;
data.color[2] = 0.0f;
data.color[3] = 1.0f;
data.scale[0] = 2.0f;
data.scale[1] = 2.0f;
data.intensity = 0.8f;
ubo.write(&data, sizeof(data));
}
```
### 6.3 多个 UBO 绑定
```glsl
// 可以在 Set 1 定义多个 UBO
layout(set = 1, binding = 0, std140) uniform MaterialParams {
vec4 albedo;
float roughness;
float metallic;
vec2 _pad;
} material;
layout(set = 1, binding = 1, std140) uniform LightParams {
vec4 light_pos;
vec4 light_color;
float intensity;
float _pad[3];
} light;
```
---
## 7. 完整示例:自定义效果控件
### 7.1 创建波纹效果
**着色器 (ripple.frag)**
```glsl
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
// Set 1: 波纹参数
layout(set = 1, binding = 0, std140) uniform RippleParams {
float time; // 当前时间
float frequency; // 波纹频率
float amplitude; // 波纹振幅
float decay; // 衰减系数
} params;
// Push Constant: 波纹中心和颜色
layout(push_constant) uniform PushConstants {
vec4 color;
vec2 center;
float _pad[2];
} pc;
void main() {
// 计算到中心的距离
float dist = length(v_local_pos - pc.center);
// 计算波纹
float wave = sin(dist * params.frequency - params.time * 3.0);
wave *= params.amplitude * exp(-dist * params.decay);
// 应用波纹到颜色亮度
vec4 color = pc.color;
color.rgb *= 1.0 + wave;
frag_color = color;
frag_color.a *= widget.u_opacity;
}
```
**C++ 使用:**
```cpp
// UBO 结构
struct alignas(16) RippleParamsUBO {
float time;
float frequency;
float amplitude;
float decay;
};
// Push Constant 结构
struct RipplePushConstants {
float color[4];
float center[2];
float _pad[2];
};
class ripple_widget {
public:
void set_center(float x, float y) {
push_constants_.center[0] = x;
push_constants_.center[1] = y;
}
void set_color(float r, float g, float b, float a) {
push_constants_.color[0] = r;
push_constants_.color[1] = g;
push_constants_.color[2] = b;
push_constants_.color[3] = a;
}
void update(float delta_time) {
params_.time += delta_time;
ubo_->write(&params_, sizeof(params_));
}
void render(vk::CommandBuffer cmd) {
cmd.pushConstants(
pipeline_layout_,
vk::ShaderStageFlagBits::eFragment,
0,
sizeof(push_constants_),
&push_constants_
);
// ... 绑定管线和描述符集,绘制
}
private:
RippleParamsUBO params_{0.0f, 10.0f, 0.1f, 2.0f};
RipplePushConstants push_constants_{};
std::unique_ptr<buffer> ubo_;
vk::PipelineLayout pipeline_layout_;
};
```
---
## 8. 相关文档
- [Shader Widget 架构设计](shader_widget_architecture.md) - 详细的架构设计文档
- [ADR-003: 迁移到 GLSL 着色器系统](adr/adr-003-glsl-shader-system.md) - 架构决策记录
- [GLSL 着色器系统设计](../plans/glsl_shader_system_design.md) - 详细设计文档

View File

@@ -0,0 +1,598 @@
# MIRAI GLSL 着色器系统架构设计
## 1. 系统架构概述
### 1.1 设计目标
1. **从 Slang 迁移到 GLSL**:使用标准 GLSL
2. **Push Constant 完全留给用户**:框架不使用
3. **框架数据最小化**:只提供 bounds 和 opacity
4. **简单约定**:提前约定框架和用户各自使用的资源
5. **无 include 机制**:提供模板让用户复制
### 1.2 四种渲染模式
| 模式 | 顶点着色器 | 片段着色器 | 说明 |
|------|-----------|-----------|------|
| Backdrop | 框架提供 | 用户提供 | 后处理效果,框架提供背景纹理 |
| Procedural | 框架提供 | 用户提供 | 程序化生成 |
| Mask | 框架提供 | 用户提供 | 遮罩效果 |
| Custom Mesh | 用户提供 | 用户提供 | 自定义网格渲染 |
---
## 2. 资源分配约定
```
┌─────────────────────────────────────────────────────────────┐
│ 资源分配约定表 │
├─────────────────┬───────────────────────────────────────────┤
│ 资源类型 │ 分配 │
├─────────────────┼───────────────────────────────────────────┤
│ Set 0 │ 框架专用(用户不要使用) │
│ Set 1 │ 用户 UBO │
│ Set 2 │ 用户纹理 │
│ Push Constant │ 用户专用(最大 128 bytes
├─────────────────┼───────────────────────────────────────────┤
│ 顶点着色器 │ 框架提供Custom Mesh 除外) │
│ 片段着色器 │ 用户提供 │
└─────────────────┴───────────────────────────────────────────┘
```
---
## 3. Set 0框架专用用户不要使用
### 3.1 通用模式Procedural, Mask
```glsl
// ============================================================
// Set 0: 框架专用 - 用户不要在 Set 0 定义任何资源
// ============================================================
// Binding 0: Widget 边界
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds; // (x, y, width, height)
float u_opacity; // 0.0 - 1.0
float _pad0;
float _pad1;
float _pad2;
} widget;
// 大小: 32 bytes
```
### 3.2 Backdrop 模式
```glsl
// Binding 0: Widget 边界(同上)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0;
float _pad1;
float _pad2;
} widget;
// Binding 1: 背景纹理(框架自动绑定)
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
```
---
## 4. 框架内置顶点着色器
框架为 Backdrop/Procedural/Mask 模式提供内置顶点着色器,用户无需编写。
### 4.1 内置顶点着色器输出
框架的顶点着色器会输出以下数据给片段着色器:
```glsl
// 框架顶点着色器输出(用户片段着色器的输入)
layout(location = 0) in vec2 v_uv; // UV 坐标 (0-1)
layout(location = 1) in vec2 v_local_pos; // 局部坐标 (0-1),相对于 widget bounds
```
### 4.2 框架内置顶点着色器实现(参考)
```glsl
// 框架内部实现,用户无需关心
#version 450
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_uv;
layout(location = 0) out vec2 v_uv;
layout(location = 1) out vec2 v_local_pos;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0;
float _pad1;
float _pad2;
} widget;
void main() {
// 将顶点位置转换到 NDC
vec2 pos = a_position * widget.u_bounds.zw + widget.u_bounds.xy;
vec2 ndc = pos * 2.0 - 1.0;
gl_Position = vec4(ndc, 0.0, 1.0);
v_uv = a_uv;
v_local_pos = a_position;
}
```
---
## 5. 用户片段着色器模板
### 5.1 Procedural 模式模板
用户只需编写片段着色器,复制以下模板:
```glsl
#version 450
// ============================================================
// 框架输入(不要修改)
// ============================================================
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用(不要修改)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds; // (x, y, width, height)
float u_opacity;
float _pad0;
float _pad1;
float _pad2;
} widget;
// ============================================================
// 用户区域(自由定义)
// ============================================================
// Set 1: 用户 UBO可选
layout(set = 1, binding = 0, std140) uniform MyParams {
float time;
// ... 自定义参数
} params;
// Set 2: 用户纹理(可选)
// layout(set = 2, binding = 0) uniform sampler2D u_my_texture;
// Push Constant可选最大 128 bytes
layout(push_constant) uniform PushConstants {
vec4 color;
// ... 自定义参数
} pc;
// ============================================================
// 主函数
// ============================================================
void main() {
// 用户实现...
frag_color = pc.color;
frag_color.a *= widget.u_opacity;
}
```
### 5.2 Backdrop 模式模板
```glsl
#version 450
// ============================================================
// 框架输入(不要修改)
// ============================================================
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用(不要修改)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0;
float _pad1;
float _pad2;
} widget;
// 背景纹理(框架提供)
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
// ============================================================
// 用户区域(自由定义)
// ============================================================
// Set 1: 用户 UBO可选
layout(set = 1, binding = 0, std140) uniform BlurParams {
float radius;
int samples;
vec2 texel_size;
} params;
// Push Constant可选
layout(push_constant) uniform PushConstants {
// ... 自定义参数
} pc;
// ============================================================
// 主函数
// ============================================================
void main() {
// 采样背景纹理
vec4 color = texture(u_backdrop, v_uv);
// 用户处理...
frag_color = color;
frag_color.a *= widget.u_opacity;
}
```
### 5.3 Mask 模式模板
```glsl
#version 450
// ============================================================
// 框架输入(不要修改)
// ============================================================
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用(不要修改)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0;
float _pad1;
float _pad2;
} widget;
// ============================================================
// 用户区域(自由定义)
// ============================================================
// Push Constant可选
layout(push_constant) uniform PushConstants {
vec4 color;
float corner_radius;
float _pad[3];
} pc;
// ============================================================
// 主函数
// ============================================================
void main() {
// 计算遮罩(示例:圆角矩形)
vec2 size = widget.u_bounds.zw;
vec2 p = (v_local_pos - 0.5) * size;
vec2 q = abs(p) - size * 0.5 + pc.corner_radius;
float d = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - pc.corner_radius;
float mask = 1.0 - smoothstep(-1.0, 1.0, d);
frag_color = pc.color;
frag_color.a *= mask * widget.u_opacity;
}
```
### 5.4 Custom Mesh 模式模板
Custom Mesh 模式需要用户同时提供顶点和片段着色器:
**顶点着色器:**
```glsl
#version 450
// ============================================================
// 用户定义顶点输入
// ============================================================
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec2 a_uv;
layout(location = 2) in vec4 a_color;
layout(location = 0) out vec2 v_uv;
layout(location = 1) out vec4 v_color;
// Set 0: 框架专用(不要修改)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0;
float _pad1;
float _pad2;
} widget;
// ============================================================
// 用户区域(自由定义)
// ============================================================
// Push Constant
layout(push_constant) uniform PushConstants {
mat4 mvp;
} pc;
// ============================================================
// 主函数
// ============================================================
void main() {
gl_Position = pc.mvp * vec4(a_position, 1.0);
v_uv = a_uv;
v_color = a_color;
}
```
**片段着色器:**
```glsl
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec4 v_color;
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用(不要修改)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0;
float _pad1;
float _pad2;
} widget;
// ============================================================
// 用户区域(自由定义)
// ============================================================
// Set 2: 用户纹理(可选)
layout(set = 2, binding = 0) uniform sampler2D u_texture;
void main() {
frag_color = texture(u_texture, v_uv) * v_color;
frag_color.a *= widget.u_opacity;
}
```
---
## 6. 示例着色器
### 6.1 纯色填充Procedural
```glsl
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
layout(push_constant) uniform PC {
vec4 color;
} pc;
void main() {
frag_color = pc.color;
frag_color.a *= widget.u_opacity;
}
```
### 6.2 线性渐变Procedural
```glsl
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
layout(push_constant) uniform PC {
vec4 color_start;
vec4 color_end;
float angle;
float _pad[3];
} pc;
void main() {
vec2 dir = vec2(cos(pc.angle), sin(pc.angle));
float t = dot(v_local_pos - 0.5, dir) + 0.5;
frag_color = mix(pc.color_start, pc.color_end, clamp(t, 0.0, 1.0));
frag_color.a *= widget.u_opacity;
}
```
### 6.3 高斯模糊Backdrop
```glsl
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
layout(set = 1, binding = 0, std140) uniform BlurParams {
float sigma;
int samples;
vec2 texel_size;
} params;
void main() {
vec4 color = vec4(0.0);
float total = 0.0;
for (int i = -params.samples; i <= params.samples; i++) {
for (int j = -params.samples; j <= params.samples; j++) {
vec2 offset = vec2(i, j) * params.texel_size;
float w = exp(-(float(i*i + j*j)) / (2.0 * params.sigma * params.sigma));
color += texture(u_backdrop, v_uv + offset) * w;
total += w;
}
}
frag_color = color / total;
frag_color.a *= widget.u_opacity;
}
```
### 6.4 圆角矩形Mask
```glsl
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
layout(push_constant) uniform PC {
vec4 color;
float radius;
float _pad[3];
} pc;
void main() {
vec2 size = widget.u_bounds.zw;
vec2 half_size = size * 0.5;
vec2 p = (v_local_pos - 0.5) * size;
vec2 q = abs(p) - half_size + pc.radius;
float d = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - pc.radius;
float alpha = 1.0 - smoothstep(-1.0, 1.0, d);
frag_color = pc.color;
frag_color.a *= alpha * widget.u_opacity;
}
```
---
## 7. C++ 端实现Vulkan HPP
### 7.1 框架 UBO 结构
```cpp
namespace mirai {
struct alignas(16) widget_bounds_ubo {
float bounds[4]; // x, y, width, height
float opacity;
float _pad[3];
};
static_assert(sizeof(widget_bounds_ubo) == 32);
} // namespace mirai
```
### 7.2 描述符集布局创建
```cpp
namespace mirai {
// 通用模式 Set 0 布局
inline vk::UniqueDescriptorSetLayout create_set0_layout_common(vk::Device device) {
vk::DescriptorSetLayoutBinding binding{
0, vk::DescriptorType::eUniformBuffer, 1,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment
};
return device.createDescriptorSetLayoutUnique({{}, 1, &binding});
}
// Backdrop 模式 Set 0 布局
inline vk::UniqueDescriptorSetLayout create_set0_layout_backdrop(vk::Device device) {
std::array<vk::DescriptorSetLayoutBinding, 2> bindings = {{
{0, vk::DescriptorType::eUniformBuffer, 1,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
{1, vk::DescriptorType::eCombinedImageSampler, 1,
vk::ShaderStageFlagBits::eFragment}
}};
return device.createDescriptorSetLayoutUnique({{}, bindings});
}
} // namespace mirai
```
---
## 8. 编译系统
### 8.1 编译流程
```
用户 GLSL 片段着色器 → glslangValidator → SPIR-V → spirv-cross(反射)
```
### 8.2 CMake 集成
```cmake
function(compile_glsl_shader INPUT OUTPUT)
add_custom_command(
OUTPUT ${OUTPUT}
COMMAND glslangValidator -V ${INPUT} -o ${OUTPUT}
DEPENDS ${INPUT}
COMMENT "Compiling ${INPUT}"
)
endfunction()
```
---
## 9. 总结
### 资源分配
| 资源 | 归属 | 说明 |
|------|------|------|
| Set 0 | 框架 | WidgetBounds + Backdrop纹理 |
| Set 1 | 用户 | 用户 UBO |
| Set 2 | 用户 | 用户纹理 |
| Push Constant | 用户 | 最大 128 bytes |
| 顶点着色器 | 框架 | Custom Mesh 除外 |
| 片段着色器 | 用户 | 所有模式 |
### 框架提供的数据
| 数据 | 位置 | 类型 |
|------|------|------|
| u_bounds | Set 0, Binding 0 | vec4 |
| u_opacity | Set 0, Binding 0 | float |
| u_backdrop | Set 0, Binding 1 | sampler2D (仅 Backdrop) |
### 框架顶点着色器输出
| 输出 | location | 类型 | 说明 |
|------|----------|------|------|
| v_uv | 0 | vec2 | UV 坐标 (0-1) |
| v_local_pos | 1 | vec2 | 局部坐标 (0-1) |

File diff suppressed because it is too large Load Diff

View File

@@ -1,317 +0,0 @@
# Vulkan C API 到 vulkan.hpp 迁移计划
## 1. 概述
本文档分析了 mirai 项目中 Vulkan C API 的使用情况,并提供迁移到 vulkan.hpp (Vulkan C++ 绑定) 的详细计划。
## 2. 需要迁移的文件
### 2.1 src/render/ 目录
| 文件 | Vulkan 类型使用情况 | 迁移优先级 |
|------|-------------------|-----------|
| [`vulkan_types.hpp`](../src/render/vulkan_types.hpp) | VkResult, VkSurfaceCapabilitiesKHR, VkSurfaceFormatKHR, VkPresentModeKHR, VkExtent2D, VkPhysicalDevice, VkPhysicalDeviceProperties, VkPhysicalDeviceFeatures, VkPhysicalDeviceVulkan11/12/13Features, VkPhysicalDeviceMemoryProperties, VkFormat, VkColorSpaceKHR | **高** - 核心类型定义 |
| [`vulkan_instance.hpp`](../src/render/vulkan_instance.hpp) | VkInstance, VkDebugUtilsMessengerEXT, VkExtensionProperties, VkLayerProperties, VkSurfaceKHR | **高** - 实例管理 |
| [`vulkan_device.hpp`](../src/render/vulkan_device.hpp) | VkDevice, VkPhysicalDevice, VkQueue, VkMemoryPropertyFlags, VkFormat, VkImageTiling, VkFormatFeatureFlags | **高** - 设备管理 |
| [`swapchain.hpp`](../src/render/swapchain.hpp) | VkSwapchainKHR, VkImage, VkImageView, VkSurfaceFormatKHR, VkExtent2D, VkPresentModeKHR, VkSemaphore, VkFence | **高** - 交换链 |
| [`command_buffer.hpp`](../src/render/command_buffer.hpp) | VkCommandPool, VkCommandBuffer, VkCommandBufferLevel, VkCommandBufferUsageFlags, VkDependencyInfo, VkPipeline, VkBuffer, VkIndexType, VkDescriptorSet, VkPipelineLayout, VkRect2D, VkExtent2D | **中** - 命令缓冲 |
| [`pipeline.hpp`](../src/render/pipeline.hpp) | VkPipelineLayout, VkPipeline, VkShaderModule, VkFormat, VkVertexInputRate, VkPrimitiveTopology, VkPolygonMode, VkCullModeFlags, VkFrontFace, VkSampleCountFlagBits, VkCompareOp, VkStencilOpState, VkBlendFactor, VkBlendOp, VkColorComponentFlags, VkDynamicState, VkDescriptorSetLayout, VkPushConstantRange | **中** - 管线 |
| [`frame_sync.hpp`](../src/render/frame_sync.hpp) | VkSemaphore, VkFence | **低** |
| [`render_pass.hpp`](../src/render/render_pass.hpp) | VkRenderPass, VkFramebuffer | **低** (可能已使用 Dynamic Rendering) |
| [`batch.hpp`](../src/render/batch.hpp) | 可能使用 Vulkan 类型 | **低** |
| [`renderer.hpp`](../src/render/renderer.hpp) | 组合使用其他模块 | **低** |
### 2.2 src/resource/ 目录
| 文件 | Vulkan 类型使用情况 | 迁移优先级 |
|------|-------------------|-----------|
| [`resource_types.hpp`](../src/resource/resource_types.hpp) | 可能定义 Vulkan 相关枚举映射 | **中** |
| [`allocator.hpp`](../src/resource/allocator.hpp) | VmaAllocator, VkBuffer, VkImage, VmaAllocation | **高** - VMA 集成 |
| [`buffer.hpp`](../src/resource/buffer.hpp) | VkBuffer, VkDeviceAddress, VkCommandBuffer, VkDescriptorBufferInfo, VkDeviceSize | **中** |
| [`texture.hpp`](../src/resource/texture.hpp) | VkImage, VkImageSubresourceRange, VkCommandBuffer | **中** |
| [`texture_view.hpp`](../src/resource/texture_view.hpp) | VkImageView | **中** |
| [`sampler.hpp`](../src/resource/sampler.hpp) | VkSampler | **低** |
| [`descriptor_pool.hpp`](../src/resource/descriptor_pool.hpp) | VkDescriptorPool, VkDescriptorSet, VkDescriptorSetLayout, VkDescriptorBufferInfo, VkDescriptorImageInfo, VkBufferView, VkShaderStageFlags, VkSampler, VkImageLayout | **中** |
### 2.3 src/shader/ 目录
| 文件 | Vulkan 类型使用情况 | 迁移优先级 |
|------|-------------------|-----------|
| [`shader_module.hpp`](../src/shader/shader_module.hpp) | VkShaderModule, VkDevice | **中** |
| [`shader_types.hpp`](../src/shader/shader_types.hpp) | 可能包含 Vulkan 类型映射 | **中** |
| [`shader_reflection.hpp`](../src/shader/shader_reflection.hpp) | 可能使用 Vulkan 描述符类型 | **低** |
| [`shader_program.hpp`](../src/shader/shader_program.hpp) | 组合 shader_module | **低** |
## 3. Vulkan 类型使用统计
### 3.1 核心句柄类型
- `VkInstance` - 实例
- `VkPhysicalDevice` - 物理设备
- `VkDevice` - 逻辑设备
- `VkQueue` - 队列
- `VkSurfaceKHR` - 表面
- `VkSwapchainKHR` - 交换链
- `VkCommandPool` - 命令池
- `VkCommandBuffer` - 命令缓冲
- `VkBuffer` - 缓冲
- `VkImage` - 图像
- `VkImageView` - 图像视图
- `VkSampler` - 采样器
- `VkPipeline` - 管线
- `VkPipelineLayout` - 管线布局
- `VkDescriptorPool` - 描述符池
- `VkDescriptorSet` - 描述符集
- `VkDescriptorSetLayout` - 描述符集布局
- `VkShaderModule` - 着色器模块
- `VkSemaphore` - 信号量
- `VkFence` - 栅栏
- `VkDebugUtilsMessengerEXT` - 调试信使
### 3.2 结构体类型
- `VkPhysicalDeviceProperties`
- `VkPhysicalDeviceFeatures`
- `VkPhysicalDeviceVulkan11Features`
- `VkPhysicalDeviceVulkan12Features`
- `VkPhysicalDeviceVulkan13Features`
- `VkPhysicalDeviceMemoryProperties`
- `VkSurfaceCapabilitiesKHR`
- `VkSurfaceFormatKHR`
- `VkExtent2D`
- `VkRect2D`
- `VkDescriptorBufferInfo`
- `VkDescriptorImageInfo`
- `VkImageSubresourceRange`
- `VkDependencyInfo`
- `VkPushConstantRange`
- `VkStencilOpState`
- `VkPipelineColorBlendAttachmentState`
- `VkExtensionProperties`
- `VkLayerProperties`
### 3.3 枚举类型
- `VkResult`
- `VkFormat`
- `VkColorSpaceKHR`
- `VkPresentModeKHR`
- `VkImageLayout`
- `VkPrimitiveTopology`
- `VkPolygonMode`
- `VkCullModeFlags`
- `VkFrontFace`
- `VkCompareOp`
- `VkBlendFactor`
- `VkBlendOp`
- `VkDynamicState`
- `VkShaderStageFlags`
- `VkVertexInputRate`
- `VkSampleCountFlagBits`
- `VkIndexType`
- `VkCommandBufferLevel`
- `VkImageTiling`
- `VkMemoryPropertyFlags`
- `VkFormatFeatureFlags`
- `VkColorComponentFlags`
## 4. 迁移策略
### 4.1 是否需要包装层
**建议:不需要创建单独的 vulkan_wrapper.hpp**
理由:
1. vulkan.hpp 本身已经是 C API 的 C++ 包装
2. 项目已有良好的抽象层vulkan_instance, vulkan_device 等类)
3. 直接在现有类中使用 vulkan.hpp 类型即可
### 4.2 迁移方式
**推荐:渐进式迁移**
```cpp
// 迁移前
#include <vulkan/vulkan.h>
VkDevice device = VK_NULL_HANDLE;
// 迁移后
#include <vulkan/vulkan.hpp>
vk::Device device; // 或 vk::UniqueDevice 用于 RAII
```
### 4.3 关键转换对照表
| C API | vulkan.hpp | 备注 |
|-------|-----------|------|
| `VkInstance` | `vk::Instance` | |
| `VkDevice` | `vk::Device` / `vk::UniqueDevice` | UniqueDevice 自动管理生命周期 |
| `VkPhysicalDevice` | `vk::PhysicalDevice` | |
| `VkQueue` | `vk::Queue` | |
| `VkCommandBuffer` | `vk::CommandBuffer` | |
| `VkBuffer` | `vk::Buffer` / `vk::UniqueBuffer` | |
| `VkImage` | `vk::Image` / `vk::UniqueImage` | |
| `VK_NULL_HANDLE` | `nullptr` 或默认构造 | |
| `VK_SUCCESS` | `vk::Result::eSuccess` | |
| `vkCreateXxx()` | `device.createXxx()` | 成员函数 |
| `vkDestroyXxx()` | `device.destroyXxx()` | 或使用 UniqueXxx 自动销毁 |
| `VkResult` | `vk::Result` 或异常 | |
### 4.4 错误处理策略
vulkan.hpp 支持两种错误处理方式:
1. **异常模式**(默认):
```cpp
try {
auto device = instance.createDevice(createInfo);
} catch (vk::SystemError& e) {
// 处理错误
}
```
2. **返回值模式**
```cpp
#define VULKAN_HPP_NO_EXCEPTIONS
auto [result, device] = instance.createDevice(createInfo);
if (result != vk::Result::eSuccess) {
// 处理错误
}
```
**建议:使用返回值模式**,与项目现有的 `result<T>` 错误处理风格一致。
## 5. 迁移顺序
### 阶段 1基础设施高优先级
1. [`vulkan_types.hpp`](../src/render/vulkan_types.hpp) - 更新类型定义
2. [`vulkan_instance.hpp`](../src/render/vulkan_instance.hpp) / `.cpp`
3. [`vulkan_device.hpp`](../src/render/vulkan_device.hpp) / `.cpp`
4. [`allocator.hpp`](../src/resource/allocator.hpp) / `.cpp` - VMA 集成
### 阶段 2资源管理中优先级
5. [`swapchain.hpp`](../src/render/swapchain.hpp) / `.cpp`
6. [`buffer.hpp`](../src/resource/buffer.hpp) / `.cpp`
7. [`texture.hpp`](../src/resource/texture.hpp) / `.cpp`
8. [`texture_view.hpp`](../src/resource/texture_view.hpp) / `.cpp`
9. [`descriptor_pool.hpp`](../src/resource/descriptor_pool.hpp) / `.cpp`
### 阶段 3渲染管线中优先级
10. [`command_buffer.hpp`](../src/render/command_buffer.hpp) / `.cpp`
11. [`pipeline.hpp`](../src/render/pipeline.hpp) / `.cpp`
12. [`shader_module.hpp`](../src/shader/shader_module.hpp) / `.cpp`
### 阶段 4其他模块低优先级
13. [`frame_sync.hpp`](../src/render/frame_sync.hpp) / `.cpp`
14. [`sampler.hpp`](../src/resource/sampler.hpp) / `.cpp`
15. 其他 shader 相关文件
16. [`renderer.hpp`](../src/render/renderer.hpp) / `.cpp`
## 6. 兼容性注意事项
### 6.1 VMA (Vulkan Memory Allocator) 集成
VMA 使用 C API需要特殊处理
```cpp
// 从 vulkan.hpp 类型获取 C 句柄
vk::Device device;
VkDevice c_device = static_cast<VkDevice>(device);
// 或
VkDevice c_device = *device;
// 将 C 句柄转换为 vulkan.hpp 类型
VkBuffer c_buffer = ...;
vk::Buffer buffer(c_buffer);
```
### 6.2 与现有代码的兼容
- vulkan.hpp 类型可以隐式转换为 C 类型
- C 类型需要显式构造为 vulkan.hpp 类型
- 建议在迁移期间保持 getter 方法返回 C 类型以保持兼容
### 6.3 编译器要求
- 需要 C++17 或更高版本(项目已使用 C++20
- 需要定义 `VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1` 使用动态加载
### 6.4 CMake 配置
```cmake
# vcpkg.json 添加
{
"dependencies": [
"vulkan",
"vulkan-hpp"
]
}
# 或直接使用 Vulkan SDK 自带的 vulkan.hpp
find_package(Vulkan REQUIRED)
target_link_libraries(${PROJECT_NAME} Vulkan::Vulkan)
```
## 7. 代码示例
### 7.1 迁移前后对比
**迁移前 (vulkan_instance.cpp):**
```cpp
VkInstanceCreateInfo create_info{};
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
create_info.pApplicationInfo = &app_info;
create_info.enabledExtensionCount = static_cast<u32>(extensions.size());
create_info.ppEnabledExtensionNames = extensions.data();
VkResult result = vkCreateInstance(&create_info, nullptr, &instance_);
if (result != VK_SUCCESS) {
return error("Failed to create Vulkan instance");
}
```
**迁移后:**
```cpp
vk::InstanceCreateInfo create_info{};
create_info.setPApplicationInfo(&app_info)
.setEnabledExtensionCount(static_cast<u32>(extensions.size()))
.setPpEnabledExtensionNames(extensions.data());
auto [result, instance] = vk::createInstance(create_info);
if (result != vk::Result::eSuccess) {
return error("Failed to create Vulkan instance");
}
instance_ = instance;
```
### 7.2 使用 UniqueHandle 简化资源管理
```cpp
// 使用 UniqueDevice 自动管理设备生命周期
vk::UniqueDevice device = physicalDevice.createDeviceUnique(createInfo);
// 设备会在 unique_ptr 销毁时自动调用 vkDestroyDevice
```
## 8. 测试策略
1. **单元测试**:每个迁移的模块需要通过现有单元测试
2. **集成测试**:确保渲染管线正常工作
3. **性能测试**验证没有性能回退vulkan.hpp 是零开销抽象)
## 9. 风险评估
| 风险 | 影响 | 缓解措施 |
|------|------|---------|
| VMA 兼容性问题 | 中 | 在 allocator 层做类型转换 |
| 编译时间增加 | 低 | vulkan.hpp 主要是头文件,可能增加编译时间 |
| 调试困难 | 低 | 使用 Vulkan Validation Layers |
| 团队学习成本 | 低 | vulkan.hpp 语法直观,学习曲线平缓 |
## 10. 总结
迁移到 vulkan.hpp 将带来以下好处:
- 更安全的类型系统
- 更简洁的代码
- 更好的 IDE 支持(自动补全)
- 可选的 RAII 资源管理
- 更现代的 C++ 风格
建议采用渐进式迁移策略,从基础设施层开始,逐步扩展到其他模块。

View File

@@ -1,23 +1,15 @@
# ================================================================================================
# MIRAI Framework - App 模块
# 描述: 示应用程序
# 描述: 示应用程序
# ================================================================================================
project(mirai_demo)
simple_executable()
target_link_libraries(${PROJECT_NAME} PRIVATE
mirai_core
mirai_threading
mirai_window
mirai_resource
mirai_shader
mirai_demo_lib
mirai_render
mirai_text
mirai_input
mirai_reactive
mirai_loop
mirai_ui
mirai_debug
)
set_target_properties(${PROJECT_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin

View File

@@ -1,19 +1,50 @@
/**
* @file main.cpp
* @brief MIRAI 框架示例应用
* @brief MIRAI 框架演示应用入口
* @author MIRAI Team
* @version 0.1.0
*
* 本文件是 MIRAI 框架的示例应用入口点
* 本文件是 MIRAI 框架演示应用入口点
*/
#include <iostream>
#include "demo_app.hpp"
#include "logger.hpp"
int main(int argc, char* argv[]) {
std::cout << "MIRAI Framework v0.1.0" << std::endl;
std::cout << "=====================" << std::endl;
std::cout << "This is a placeholder demo application." << std::endl;
std::cout << "Full implementation coming in later phases." << std::endl;
int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) {
// 初始化日志系统
mirai::logger_config log_config;
log_config.name = "mirai_demo";
log_config.level = mirai::log_level::debug;
log_config.console_enabled = true;
mirai::init_logger(log_config);
MIRAI_LOG_INFO("========================================");
MIRAI_LOG_INFO("MIRAI Framework Demo v0.1.0");
MIRAI_LOG_INFO("========================================");
// 创建演示应用配置
mirai::demo::demo_window_config config;
config.width = 1280;
config.height = 720;
config.title = "MIRAI Demo";
config.resizable = true;
config.vsync = true;
config.high_dpi = true;
// 创建并运行演示应用
mirai::demo::demo_app app(config);
if (!app.initialize()) {
MIRAI_LOG_ERROR("Failed to initialize demo application");
mirai::shutdown_logger();
return 1;
}
app.run();
app.shutdown();
MIRAI_LOG_INFO("Demo application exited normally");
mirai::shutdown_logger();
return 0;
}

View File

@@ -23,440 +23,433 @@
#include "types/types.hpp"
namespace mirai {
// ================================================================================================
// 前置声明
// ================================================================================================
// ================================================================================================
// 前置声明
// ================================================================================================
class object;
class object_registry;
class object;
class object_registry;
// make_obj 辅助结构体前置声明
struct object_factory;
// make_obj 辅助结构体前置声明
struct object_factory;
// ================================================================================================
// 对象 ID 类型
// ================================================================================================
// ================================================================================================
// 对象 ID 类型
// ================================================================================================
/**
* @brief 对象唯一标识符类型
*
* 每个 object 实例都有一个唯一的 ID用于调试和追踪
*/
using object_id = u64;
/**
* @brief 对象唯一标识符类型
*
* 每个 object 实例都有一个唯一的 ID用于调试和追踪
*/
using object_id = u64;
/**
* @brief 无效的对象 ID
*/
constexpr object_id invalid_object_id = 0;
/**
* @brief 无效的对象 ID
*/
constexpr object_id invalid_object_id = 0;
// ================================================================================================
// 类型信息
// ================================================================================================
// ================================================================================================
// 类型信息
// ================================================================================================
/**
* @brief 类型信息结构
*
* 提供运行时类型信息,作为 RTTI 的替代方案
*/
struct type_info {
/// 类型名称
std::string_view name;
/**
* @brief 类型信息结构
*
* 提供运行时类型信息,作为 RTTI 的替代方案
*/
struct type_info {
/// 类型名称
std::string_view name;
/// 类型哈希值(用于快速比较)
size_type hash;
/// 父类型信息(如果有)
const type_info* parent;
/**
* @brief 检查是否与另一个类型相同
* @param other 另一个类型信息
* @return 如果相同返回 true
*/
[[nodiscard]] constexpr bool operator==(const type_info& other) const noexcept {
return hash == other.hash;
}
/**
* @brief 检查是否与另一个类型不同
* @param other 另一个类型信息
* @return 如果不同返回 true
*/
[[nodiscard]] constexpr bool operator!=(const type_info& other) const noexcept {
return hash != other.hash;
}
/**
* @brief 检查此类型是否派生自另一个类型
* @param base 基类类型信息
* @return 如果是派生类返回 true
*/
[[nodiscard]] bool is_derived_from(const type_info& base) const noexcept {
const type_info* current = this;
while (current != nullptr) {
if (*current == base) {
return true;
}
current = current->parent;
}
return false;
}
};
/// 类型哈希值(用于快速比较)
size_type hash;
namespace detail {
/// 父类型信息(如果有)
const type_info* parent;
/**
* @brief 编译时计算字符串哈希值
* @param str 字符串
* @return 哈希值
*/
constexpr size_type compile_time_hash(std::string_view str) noexcept {
size_type hash = 14695981039346656037ULL;
for (char c : str) {
hash ^= static_cast<size_type>(c);
hash *= 1099511628211ULL;
}
return hash;
}
/**
* @brief 检查是否与另一个类型相同
* @param other 另一个类型信息
* @return 如果相同返回 true
*/
[[nodiscard]] constexpr bool operator==(const type_info& other) const noexcept {
return hash == other.hash;
}
/**
* @brief 获取类型名称
* @tparam T 类型
* @return 类型名称字符串
*/
template<typename T>
consteval std::string_view get_type_name() noexcept {
#if defined(__clang__)
constexpr std::string_view prefix = "[T = ";
constexpr std::string_view suffix = "]";
constexpr std::string_view function = __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
constexpr std::string_view prefix = "with T = ";
constexpr std::string_view suffix = "]";
constexpr std::string_view function = __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
constexpr std::string_view prefix = "get_type_name<";
constexpr std::string_view suffix = ">(void)";
constexpr std::string_view function = __FUNCSIG__;
#else
return typeid(T).name();
#endif
const auto start = function.find(prefix) + prefix.size();
const auto end = function.rfind(suffix);
return function.substr(start, end - start);
}
/**
* @brief 检查是否与另一个类型不同
* @param other 另一个类型信息
* @return 如果不同返回 true
*/
[[nodiscard]] constexpr bool operator!=(const type_info& other) const noexcept {
return hash != other.hash;
}
} // namespace detail
/**
* @brief 检查此类型是否派生自另一个类型
* @param base 基类类型信息
* @return 如果是派生类返回 true
*/
[[nodiscard]] bool is_derived_from(const type_info& base) const noexcept {
const type_info* current = this;
while (current != nullptr) {
if (*current == base) {
return true;
}
current = current->parent;
}
return false;
}
};
/**
* @brief 获取类型的类型信息
* @tparam T 类型
* @return 类型信息的常量引用
*/
template<typename T>
[[nodiscard]] const type_info& get_type_info() noexcept {
static const type_info info{
detail::get_type_name<T>(),
detail::compile_time_hash(detail::get_type_name<T>()),
nullptr
};
return info;
}
namespace detail {
/**
* @brief 编译时计算字符串哈希值
* @param str 字符串
* @return 哈希值
*/
constexpr size_type compile_time_hash(std::string_view str) noexcept {
size_type hash = 14695981039346656037ULL;
for (char c : str) {
hash ^= static_cast<size_type>(c);
hash *= 1099511628211ULL;
}
return hash;
}
/**
* @brief 获取带父类的类型信息
* @tparam T 类型
* @tparam Parent 父类类型
* @return 类型信息的常量引用
*/
template<typename T, typename Parent>
[[nodiscard]] const type_info& get_type_info_with_parent() noexcept {
static const type_info info{
detail::get_type_name<T>(),
detail::compile_time_hash(detail::get_type_name<T>()),
&get_type_info<Parent>()
};
return info;
}
/**
* @brief 获取类型名称
* @tparam T 类型
* @return 类型名称字符串
*/
template <typename T>
consteval std::string_view get_type_name() noexcept {
#if defined(__clang__)
constexpr std::string_view prefix = "[T = ";
constexpr std::string_view suffix = "]";
constexpr std::string_view function = __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
constexpr std::string_view prefix = "with T = ";
constexpr std::string_view suffix = "]";
constexpr std::string_view function = __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
constexpr std::string_view prefix = "get_type_name<";
constexpr std::string_view suffix = ">(void)";
constexpr std::string_view function = __FUNCSIG__;
#else
return typeid(T).name();
#endif
// ================================================================================================
// 对象基类
// ================================================================================================
const auto start = function.find(prefix) + prefix.size();
const auto end = function.rfind(suffix);
return function.substr(start, end - start);
}
} // namespace detail
/**
* @brief 对象基类
*
* 所有 MIRAI 框架对象的基类,提供:
* - 共享指针支持(通过 enable_shared_from_this
* - 类型安全的指针转换
* - 唯一对象标识符
* - 运行时类型信息
*
* @note 对象必须通过 make_object<T>() 工厂函数创建
* @note 对象禁止拷贝,但允许移动
*
* @example
* @code
* class my_widget : public object {
* public:
* MIRAI_OBJECT_TYPE_INFO(my_widget, object)
*
* my_widget(std::string name) : name_(std::move(name)) {}
*
* protected:
* void on_created() override {
* // 初始化逻辑
* }
*
* private:
* std::string name_;
* };
*
* auto widget = make_object<my_widget>("test");
* auto shared = widget->shared_from_this_as<my_widget>();
* @endcode
*/
class object : public std::enable_shared_from_this<object> {
public:
/// 共享指针类型别名
using ptr = std::shared_ptr<object>;
/// 弱指针类型别名
using weak_ptr = std::weak_ptr<object>;
/**
* @brief 虚析构函数
*/
virtual ~object();
// 禁止拷贝
object(const object&) = delete;
object& operator=(const object&) = delete;
// 禁止移动(因为只能通过智能指针管理)
object(object&&) = delete;
object& operator=(object&&) = delete;
// ============================================================================================
// 类型信息
// ============================================================================================
/**
* @brief 获取对象的类型信息
* @return 类型信息的常量引用
*/
[[nodiscard]] virtual const type_info& get_type() const noexcept {
return get_type_info<object>();
}
/**
* @brief 获取对象的类型名称
* @return 类型名称字符串
*/
[[nodiscard]] std::string_view type_name() const noexcept {
return get_type().name;
}
/**
* @brief 检查对象是否为指定类型
* @tparam T 要检查的类型
* @return 如果是指定类型返回 true
*/
template<typename T> requires std::derived_from<T, object>
[[nodiscard]] bool is() const noexcept {
return get_type().is_derived_from(get_type_info<T>());
}
/**
* @brief 安全地将对象转换为指定类型
* @tparam T 目标类型
* @return 如果转换成功返回指向对象的指针,否则返回 nullptr
*/
template<typename T>
requires std::derived_from<T, object>
[[nodiscard]] T* as() noexcept {
if (is<T>()) {
return static_cast<T*>(this);
}
return nullptr;
}
/**
* @brief 安全地将对象转换为指定类型const 版本)
* @tparam T 目标类型
* @return 如果转换成功返回指向对象的常量指针,否则返回 nullptr
*/
template<typename T>
requires std::derived_from<T, object>
[[nodiscard]] const T* as() const noexcept {
if (is<T>()) {
return static_cast<const T*>(this);
}
return nullptr;
}
/**
* @brief 获取类型的类型信息
* @tparam T 类型
* @return 类型信息的常量引用
*/
template <typename T>
[[nodiscard]] const type_info& get_type_info() noexcept {
static const type_info info{
detail::get_type_name<T>(),
detail::compile_time_hash(detail::get_type_name<T>()),
nullptr
};
return info;
}
#if MIRAI_DEBUG
void set_debug_name(std::string name) {
debug_name_ = std::move(name);
}
/**
* @brief 获取带父类的类型信息
* @tparam T 类型
* @tparam Parent 父类类型
* @return 类型信息的常量引用
*/
template <typename T, typename Parent>
[[nodiscard]] const type_info& get_type_info_with_parent() noexcept {
static const type_info info{
detail::get_type_name<T>(),
detail::compile_time_hash(detail::get_type_name<T>()),
&get_type_info<Parent>()
};
return info;
}
[[nodiscard]] const std::string& get_debug_name() const noexcept {
return debug_name_;
}
#endif
// ============================================================================================
// 对象标识
// ============================================================================================
/**
* @brief 获取对象的唯一标识符
* @return 对象 ID
*/
[[nodiscard]] object_id id() const noexcept {
return id_;
}
// ============================================================================================
// 共享指针支持
// ============================================================================================
/**
* @brief 获取指向此对象的共享指针
* @tparam T 目标类型,默认为 object
* @return 共享指针
* @throws std::bad_weak_ptr 如果对象不是由共享指针管理
*/
template<typename T = object>
requires std::derived_from<T, object>
[[nodiscard]] std::shared_ptr<T> shared_from_this_as() {
return std::dynamic_pointer_cast<T>(shared_from_this());
}
/**
* @brief 获取指向此对象的共享指针const 版本)
* @tparam T 目标类型,默认为 object
* @return 常量共享指针
* @throws std::bad_weak_ptr 如果对象不是由共享指针管理
*/
template<typename T = object>
requires std::derived_from<T, object>
[[nodiscard]] std::shared_ptr<const T> shared_from_this_as() const {
return std::dynamic_pointer_cast<const T>(shared_from_this());
}
/**
* @brief 获取指向此对象的弱指针
* @tparam T 目标类型,默认为 object
* @return 弱指针
*/
template<typename T = object>
requires std::derived_from<T, object>
[[nodiscard]] std::weak_ptr<T> weak_from_this_as() noexcept {
return std::weak_ptr<T>(shared_from_this_as<T>());
}
/**
* @brief 获取指向此对象的弱指针const 版本)
* @tparam T 目标类型,默认为 object
* @return 常量弱指针
*/
template<typename T = object>
requires std::derived_from<T, object>
[[nodiscard]] std::weak_ptr<const T> weak_from_this_as() const noexcept {
return std::weak_ptr<const T>(shared_from_this_as<T>());
}
// ============================================================================================
// 生命周期回调
// ============================================================================================
protected:
/**
* @brief 默认构造函数protected只能通过工厂函数创建
*/
object();
/**
* @brief 对象创建后的回调
*
* 在 make_object<T>() 创建对象后调用
* 子类可以重写此方法进行初始化
*/
virtual void on_created() {}
/**
* @brief 对象销毁前的回调
*
* 在智能指针的 deleter 中调用,析构函数之前
* 子类可以重写此方法进行清理
*
* @note 此时 shared_from_this() 仍然可用
*/
virtual void on_destroying() {}
private:
/// 对象唯一标识符
object_id id_;
// ================================================================================================
// 对象基类
// ================================================================================================
#if MIRAI_DEBUG
std::string debug_name_;
#endif
/// 全局对象 ID 计数器
static std::atomic<object_id> next_id_;
/**
* @brief 对象基类
*
* 所有 MIRAI 框架对象的基类,提供:
* - 共享指针支持(通过 enable_shared_from_this
* - 类型安全的指针转换
* - 唯一对象标识符
* - 运行时类型信息
*
* @note 对象必须通过 make_object<T>() 工厂函数创建
* @note 对象禁止拷贝,但允许移动
*
* @example
* @code
* class my_widget : public object {
* public:
* MIRAI_OBJECT_TYPE_INFO(my_widget, object)
*
* my_widget(std::string name) : name_(std::move(name)) {}
*
* protected:
* void on_created() override {
* // 初始化逻辑
* }
*
* private:
* std::string name_;
* };
*
* auto widget = make_object<my_widget>("test");
* auto shared = widget->shared_from_this_as<my_widget>();
* @endcode
*/
class object : public std::enable_shared_from_this<object> {
public:
/// 共享指针类型别名
using ptr = std::shared_ptr<object>;
/// 允许注册表访问
friend class object_registry;
};
/// 弱指针类型别名
using weak_ptr = std::weak_ptr<object>;
// ================================================================================================
// 自定义删除器
// ================================================================================================
/**
* @brief 虚析构函数
*/
virtual ~object();
/**
* @brief 对象删除器
*
* 在删除对象前调用 on_destroying() 回调
*
* @tparam T 对象类型,必须派生自 object
*/
template<typename T>
struct object_deleter {
void operator()(T* ptr) const noexcept {
if (ptr) {
// 在析构前调用 on_destroying此时 shared_from_this 仍可用
ptr->on_destroying();
delete ptr;
}
}
};
// 禁止拷贝
object(const object&) = delete;
object& operator=(const object&) = delete;
// ================================================================================================
// 类型信息宏
// ================================================================================================
// 禁止移动(因为只能通过智能指针管理)
object(object&&) = delete;
object& operator=(object&&) = delete;
/**
* @def MIRAI_OBJECT_TYPE_INFO(class_name, parent_class)
* @brief 声明对象类型信息
*
* 在派生自 object 的类中使用此宏来声明类型信息
*
* @param class_name 当前类名
* @param parent_class 父类名
*
* @example
* @code
* class my_widget : public object {
* public:
* MIRAI_OBJECT_TYPE_INFO(my_widget, object)
* // ...
* };
* @endcode
*/
#define MIRAI_OBJECT_TYPE_INFO(class_name, parent_class) \
// ============================================================================================
// 类型信息
// ============================================================================================
/**
* @brief 获取对象的类型信息
* @return 类型信息的常量引用
*/
[[nodiscard]] virtual const type_info& get_type() const noexcept {
return get_type_info<object>();
}
/**
* @brief 获取对象的类型名称
* @return 类型名称字符串
*/
[[nodiscard]] std::string_view type_name() const noexcept {
return get_type().name;
}
/**
* @brief 检查对象是否为指定类型
* @tparam T 要检查的类型
* @return 如果是指定类型返回 true
*/
template <typename T> requires std::derived_from<T, object>
[[nodiscard]] bool is() const noexcept {
return get_type().is_derived_from(get_type_info<T>());
}
/**
* @brief 安全地将对象转换为指定类型
* @tparam T 目标类型
* @return 如果转换成功返回指向对象的指针,否则返回 nullptr
*/
template <typename T> requires std::derived_from<T, object>
[[nodiscard]] T* as() noexcept {
if (is<T>()) {
return static_cast<T*>(this);
}
return nullptr;
}
/**
* @brief 安全地将对象转换为指定类型const 版本)
* @tparam T 目标类型
* @return 如果转换成功返回指向对象的常量指针,否则返回 nullptr
*/
template <typename T> requires std::derived_from<T, object>
[[nodiscard]] const T* as() const noexcept {
if (is<T>()) {
return static_cast<const T*>(this);
}
return nullptr;
}
#if MIRAI_DEBUG
void set_debug_name(std::string name) {
debug_name_ = std::move(name);
}
[[nodiscard]] const std::string& get_debug_name() const noexcept {
return debug_name_;
}
#endif
// ============================================================================================
// 对象标识
// ============================================================================================
/**
* @brief 获取对象的唯一标识符
* @return 对象 ID
*/
[[nodiscard]] object_id id() const noexcept {
return id_;
}
// ============================================================================================
// 共享指针支持
// ============================================================================================
/**
* @brief 获取指向此对象的共享指针
* @tparam T 目标类型,默认为 object
* @return 共享指针
* @throws std::bad_weak_ptr 如果对象不是由共享指针管理
*/
template <typename T = object> requires std::derived_from<T, object>
[[nodiscard]] std::shared_ptr<T> shared_from_this_as() {
return std::dynamic_pointer_cast<T>(shared_from_this());
}
/**
* @brief 获取指向此对象的共享指针const 版本)
* @tparam T 目标类型,默认为 object
* @return 常量共享指针
* @throws std::bad_weak_ptr 如果对象不是由共享指针管理
*/
template <typename T = object> requires std::derived_from<T, object>
[[nodiscard]] std::shared_ptr<const T> shared_from_this_as() const {
return std::dynamic_pointer_cast<const T>(shared_from_this());
}
/**
* @brief 获取指向此对象的弱指针
* @tparam T 目标类型,默认为 object
* @return 弱指针
*/
template <typename T = object> requires std::derived_from<T, object>
[[nodiscard]] std::weak_ptr<T> weak_from_this_as() noexcept {
return std::weak_ptr<T>(shared_from_this_as<T>());
}
/**
* @brief 获取指向此对象的弱指针const 版本)
* @tparam T 目标类型,默认为 object
* @return 常量弱指针
*/
template <typename T = object> requires std::derived_from<T, object>
[[nodiscard]] std::weak_ptr<const T> weak_from_this_as() const noexcept {
return std::weak_ptr<const T>(shared_from_this_as<T>());
}
// ============================================================================================
// 生命周期回调
// ============================================================================================
protected:
/**
* @brief 默认构造函数protected只能通过工厂函数创建
*/
object();
/**
* @brief 对象创建后的回调
*
* 在 make_object<T>() 创建对象后调用
* 子类可以重写此方法进行初始化
*/
virtual void on_created() {
}
/**
* @brief 对象销毁前的回调
*
* 在智能指针的 deleter 中调用,析构函数之前
* 子类可以重写此方法进行清理
*
* @note 此时 shared_from_this() 仍然可用
*/
virtual void on_destroying() {
}
private:
/// 对象唯一标识符
object_id id_;
#if MIRAI_DEBUG
std::string debug_name_;
#endif
/// 全局对象 ID 计数器
static std::atomic<object_id> next_id_;
/// 允许注册表访问
friend class object_registry;
};
// ================================================================================================
// 自定义删除器
// ================================================================================================
/**
* @brief 对象删除器
*
* 在删除对象前调用 on_destroying() 回调
*
* @tparam T 对象类型,必须派生自 object
*/
template <typename T>
struct object_deleter {
void operator()(T* ptr) const noexcept {
if (ptr) {
// 在析构前调用 on_destroying此时 shared_from_this 仍可用
ptr->on_destroying();
delete ptr;
}
}
};
// ================================================================================================
// 类型信息宏
// ================================================================================================
/**
* @def MIRAI_OBJECT_TYPE_INFO(class_name, parent_class)
* @brief 声明对象类型信息
*
* 在派生自 object 的类中使用此宏来声明类型信息
*
* @param class_name 当前类名
* @param parent_class 父类名
*
* @example
* @code
* class my_widget : public object {
* public:
* MIRAI_OBJECT_TYPE_INFO(my_widget, object)
* // ...
* };
* @endcode
*/
#define MIRAI_OBJECT_TYPE_INFO(class_name, parent_class) \
[[nodiscard]] const mirai::type_info& get_type() const noexcept override { \
return mirai::get_type_info_with_parent<class_name, parent_class>(); \
} \
@@ -469,56 +462,53 @@ struct object_deleter {
friend class object_registry;
// ================================================================================================
// 工厂函数
// ================================================================================================
// ================================================================================================
// 工厂函数
// ================================================================================================
/**
* @brief 创建对象的工厂函数
*
* 使用此函数创建 object 派生类的实例
* 创建后会自动调用 on_created() 回调
*
* @tparam T 要创建的对象类型,必须派生自 object
* @tparam Args 构造函数参数类型
* @param args 构造函数参数
* @return 指向新创建对象的共享指针
*
* @example
* @code
* auto widget = make_obj<my_widget>("test", 42);
* @endcode
*/
/**
* @brief 对象工厂辅助结构体
*
* 用于访问 object 派生类的 protected 构造函数和 on_created 方法
*/
struct object_factory {
template<typename T, typename... Args>
requires std::derived_from<T, object>
static std::shared_ptr<T> create(Args&&... args) {
std::shared_ptr<T> obj(new T(std::forward<Args>(args)...), object_deleter<T>{});
obj->on_created();
return obj;
}
};
/**
* @brief 创建对象的工厂函数
*
* 使用此函数创建 object 派生类的实例
* 创建后会自动调用 on_created() 回调
*
* @tparam T 要创建的对象类型,必须派生自 object
* @tparam Args 构造函数参数类型
* @param args 构造函数参数
* @return 指向新创建对象的共享指针
*
* @example
* @code
* auto widget = make_obj<my_widget>("test", 42);
* @endcode
*/
/**
* @brief 对象工厂辅助结构体
*
* 用于访问 object 派生类的 protected 构造函数和 on_created 方法
*/
struct object_factory {
template <typename T, typename... Args> requires std::derived_from<T, object>
static std::shared_ptr<T> create(Args&&... args) {
std::shared_ptr<T> obj(new T(std::forward<Args>(args)...), object_deleter<T>{});
obj->on_created();
return obj;
}
};
/**
* @brief 创建对象的工厂函数
*
* 使用此函数创建 object 派生类的实例
* 创建后会自动调用 on_created() 回调
*
* @tparam T 要创建的对象类型,必须派生自 object
* @tparam Args 构造函数参数类型
* @param args 传递给构造函数的参数
* @return 指向新创建对象的 shared_ptr
*/
template<typename T, typename... Args>
requires std::derived_from<T, object>
[[nodiscard]] std::shared_ptr<T> make_obj(Args&&... args) {
return object_factory::create<T>(std::forward<Args>(args)...);
}
} // namespace mirai
/**
* @brief 创建对象的工厂函数
*
* 使用此函数创建 object 派生类的实例
* 创建后会自动调用 on_created() 回调
*
* @tparam T 要创建的对象类型,必须派生自 object
* @tparam Args 构造函数参数类型
* @param args 传递给构造函数的参数
* @return 指向新创建对象的 shared_ptr
*/
template <typename T, typename... Args> requires std::derived_from<T, object>
[[nodiscard]] std::shared_ptr<T> make_obj(Args&&... args) {
return object_factory::create<T>(std::forward<Args>(args)...);
}
} // namespace mirai

File diff suppressed because it is too large Load Diff

View File

@@ -245,35 +245,6 @@ std::string shader_inspector::get_spirv_disassembly() const {
return "; SPIR-V disassembly would be displayed here";
}
bool shader_inspector::hot_reload() {
std::unique_lock lock(mutex_);
if (!shader_) {
return false;
}
bool success = false;
// 实际实现应该:
// 1. 重新编译着色器源文件
// 2. 创建新的着色器模块
// 3. 替换现有模块
// 4. 更新管线
// 触发回调
if (on_hot_reload_) {
lock.unlock();
on_hot_reload_(shader_, success);
}
return success;
}
void shader_inspector::set_on_hot_reload(hot_reload_callback callback) {
std::unique_lock lock(mutex_);
on_hot_reload_ = std::move(callback);
}
void shader_inspector::set_on_uniform_changed(uniform_changed_callback callback) {
std::unique_lock lock(mutex_);
on_uniform_changed_ = std::move(callback);

View File

@@ -187,39 +187,32 @@ struct shader_reflection_info {
/**
* @brief 着色器检视器
*
*
* @details
* 提供着色器调试功能,包括:
* - 查看着色器 Uniform
* - 修改 Uniform 值
* - 查看反射信息
* - 着色器热重载
*
*
* 使用示例:
* @code
* shader_inspector inspector;
*
*
* // 设置着色器
* inspector.set_shader(some_shader);
*
*
* // 获取 Uniforms
* auto uniforms = inspector.get_uniforms();
*
*
* // 获取 Uniform 值
* auto value = inspector.get_uniform_value("u_color");
*
*
* // 设置 Uniform 值
* inspector.set_uniform_value("u_color", std::vector<f32>{1.0f, 0.0f, 0.0f, 1.0f});
*
* // 热重载
* inspector.hot_reload();
* @endcode
*/
class shader_inspector {
public:
/// 热重载回调类型
using hot_reload_callback = std::function<void(shader_program*, bool success)>;
/// Uniform 变化回调类型
using uniform_changed_callback = std::function<void(const std::string& name,
const uniform_value& value)>;
@@ -396,22 +389,6 @@ public:
*/
[[nodiscard]] std::string get_spirv_disassembly() const;
// ============================================================================================
// 热重载
// ============================================================================================
/**
* @brief 热重载着色器
* @return 是否成功
*/
bool hot_reload();
/**
* @brief 设置热重载回调
* @param callback 回调函数
*/
void set_on_hot_reload(hot_reload_callback callback);
/**
* @brief 设置 Uniform 变化回调
* @param callback 回调函数
@@ -506,9 +483,6 @@ private:
/// 预设
std::unordered_map<std::string, uniform_preset> presets_;
/// 热重载回调
hot_reload_callback on_hot_reload_;
/// Uniform 变化回调
uniform_changed_callback on_uniform_changed_;

38
src/demo/CMakeLists.txt Normal file
View File

@@ -0,0 +1,38 @@
# ================================================================================================
# MIRAI Framework - Demo 模块
# 描述: 演示应用框架
# ================================================================================================
project(mirai_demo_lib)
# 添加子目录
add_subdirectory(widgets)
simple_library(STATIC)
target_link_libraries(${PROJECT_NAME} PUBLIC
mirai_core
mirai_render
mirai_input
mirai_demo_widgets
SDL3::SDL3
)
# 添加 widgets 目录到包含路径(使用生成器表达式)
target_include_directories(${PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/widgets>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/mirai/demo/widgets>
)
set_target_properties(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
EXPORT_NAME demo
OUTPUT_NAME mirai_demo_lib
)
install(TARGETS ${PROJECT_NAME}
EXPORT mirai-targets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
message(STATUS "Configured mirai_demo_lib library")

354
src/demo/demo_app.cpp Normal file
View File

@@ -0,0 +1,354 @@
/**
* @file demo_app.cpp
* @brief MIRAI 框架演示应用主类实现
* @author MIRAI Team
* @version 0.1.0
*/
#include "demo_app.hpp"
#include <SDL3/SDL_vulkan.h>
namespace mirai::demo {
// ================================================================================================
// 构造函数和析构函数
// ================================================================================================
demo_app::demo_app(const demo_window_config& config)
: config_(config)
, state_(demo_app_state::uninitialized)
, should_quit_(false)
, window_(nullptr)
, frame_count_(0)
, current_fps_(0.0)
, frame_time_ms_(0.0)
, fps_frame_count_(0)
{
}
demo_app::~demo_app() {
if (state_ != demo_app_state::uninitialized && state_ != demo_app_state::quit) {
shutdown();
}
}
// ================================================================================================
// 公共方法
// ================================================================================================
bool demo_app::initialize() {
MIRAI_LOG_INFO("Initializing MIRAI Demo Application...");
// 初始化 SDL
if (!init_sdl()) {
MIRAI_LOG_ERROR("Failed to initialize SDL");
return false;
}
// 创建窗口
if (!create_window()) {
MIRAI_LOG_ERROR("Failed to create window");
return false;
}
// 初始化渲染器
if (!init_renderer()) {
MIRAI_LOG_ERROR("Failed to initialize renderer");
return false;
}
// 初始化输入管理器
if (!init_input()) {
MIRAI_LOG_ERROR("Failed to initialize input manager");
return false;
}
// 初始化时间点
last_frame_time_ = std::chrono::high_resolution_clock::now();
fps_timer_ = last_frame_time_;
state_ = demo_app_state::initialized;
MIRAI_LOG_INFO("MIRAI Demo Application initialized successfully");
MIRAI_LOG_INFO("Window size: {}x{}", config_.width, config_.height);
return true;
}
void demo_app::run() {
if (state_ != demo_app_state::initialized) {
MIRAI_LOG_ERROR("Cannot run: application not initialized");
return;
}
state_ = demo_app_state::running;
MIRAI_LOG_INFO("Starting main loop...");
while (!should_quit_) {
// 计算帧时间
auto current_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<f64> delta = current_time - last_frame_time_;
f64 delta_time = delta.count();
last_frame_time_ = current_time;
frame_time_ms_ = delta_time * 1000.0;
// 处理事件
handle_events();
if (should_quit_) {
break;
}
// 更新逻辑
update(delta_time);
// 渲染
render();
// 更新帧率统计
update_fps_stats();
++frame_count_;
}
MIRAI_LOG_INFO("Main loop ended after {} frames", frame_count_);
}
void demo_app::shutdown() {
MIRAI_LOG_INFO("Shutting down MIRAI Demo Application...");
// 等待渲染器完成
if (renderer_) {
renderer_->wait_idle();
renderer_->shutdown();
renderer_.reset();
}
// 清理输入管理器
input_manager_.reset();
// 销毁窗口
if (window_) {
SDL_DestroyWindow(window_);
window_ = nullptr;
}
// 退出 SDL
SDL_Quit();
state_ = demo_app_state::quit;
MIRAI_LOG_INFO("MIRAI Demo Application shutdown complete");
}
// ================================================================================================
// 私有方法 - 初始化
// ================================================================================================
bool demo_app::init_sdl() {
MIRAI_LOG_DEBUG("Initializing SDL...");
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
MIRAI_LOG_ERROR("SDL_Init failed: {}", SDL_GetError());
return false;
}
MIRAI_LOG_DEBUG("SDL initialized successfully");
return true;
}
bool demo_app::create_window() {
MIRAI_LOG_DEBUG("Creating window...");
// 设置窗口标志
SDL_WindowFlags flags = SDL_WINDOW_VULKAN;
if (config_.resizable) {
flags |= SDL_WINDOW_RESIZABLE;
}
if (config_.high_dpi) {
flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
}
// 创建窗口
window_ = SDL_CreateWindow(
config_.title,
static_cast<int>(config_.width),
static_cast<int>(config_.height),
flags
);
if (!window_) {
MIRAI_LOG_ERROR("SDL_CreateWindow failed: {}", SDL_GetError());
return false;
}
MIRAI_LOG_DEBUG("Window created successfully");
return true;
}
bool demo_app::init_renderer() {
MIRAI_LOG_DEBUG("Initializing renderer...");
// 创建渲染器配置
renderer_config config;
config.app_name = "MIRAI Demo";
config.app_version = VK_MAKE_VERSION(0, 1, 0);
config.enable_validation = true;
config.enable_vsync = config_.vsync;
config.frames_in_flight = 2;
config.viewport_width = config_.width;
config.viewport_height = config_.height;
// 创建渲染器
renderer_ = std::make_unique<renderer>(config);
// 初始化渲染器
if (!renderer_->initialize(window_)) {
MIRAI_LOG_ERROR("Failed to initialize renderer");
return false;
}
MIRAI_LOG_DEBUG("Renderer initialized successfully");
return true;
}
bool demo_app::init_input() {
MIRAI_LOG_DEBUG("Initializing input manager...");
input_manager_ = std::make_unique<input_manager>();
input_manager_->set_window(window_);
MIRAI_LOG_DEBUG("Input manager initialized successfully");
return true;
}
// ================================================================================================
// 私有方法 - 主循环
// ================================================================================================
void demo_app::handle_events() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
// 让输入管理器处理事件
if (input_manager_) {
input_manager_->process_sdl_event(&event);
}
switch (event.type) {
case SDL_EVENT_QUIT:
MIRAI_LOG_INFO("Quit event received");
should_quit_ = true;
break;
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
MIRAI_LOG_INFO("Window close requested");
should_quit_ = true;
break;
case SDL_EVENT_KEY_DOWN:
// ESC 键退出
if (event.key.key == SDLK_ESCAPE) {
MIRAI_LOG_INFO("ESC key pressed, quitting...");
should_quit_ = true;
}
break;
case SDL_EVENT_WINDOW_RESIZED:
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
handle_window_event(event);
break;
case SDL_EVENT_WINDOW_MINIMIZED:
MIRAI_LOG_DEBUG("Window minimized");
break;
case SDL_EVENT_WINDOW_RESTORED:
MIRAI_LOG_DEBUG("Window restored");
break;
default:
break;
}
}
// 更新输入管理器状态
if (input_manager_) {
input_manager_->update(static_cast<f32>(frame_time_ms_ / 1000.0));
}
}
void demo_app::handle_window_event(const SDL_Event& event) {
if (event.type == SDL_EVENT_WINDOW_RESIZED ||
event.type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED) {
i32 width = event.window.data1;
i32 height = event.window.data2;
MIRAI_LOG_DEBUG("Window resized to {}x{}", width, height);
// 处理窗口大小变化
if (renderer_ && width > 0 && height > 0) {
if (!renderer_->on_resize(static_cast<u32>(width), static_cast<u32>(height))) {
MIRAI_LOG_WARN("Failed to handle window resize");
}
}
}
}
void demo_app::update([[maybe_unused]] f64 delta_time) {
// 基础框架暂时不需要更新逻辑
// 后续可以在这里添加演示场景的更新
}
void demo_app::render() {
if (!renderer_ || !renderer_->can_render()) {
return;
}
// 开始帧
if (!renderer_->begin_frame()) {
// 可能是窗口最小化或交换链需要重建
return;
}
// 开始渲染(使用深灰色清屏)
clear_color clear{0.1f, 0.1f, 0.1f, 1.0f};
renderer_->begin_rendering(clear);
// 这里可以添加渲染命令
// 基础框架只显示清屏颜色
// 结束渲染
renderer_->end_rendering();
// 结束帧
if (!renderer_->end_frame()) {
MIRAI_LOG_WARN("Failed to end frame");
}
// 呈现
if (!renderer_->present()) {
MIRAI_LOG_WARN("Failed to present frame");
}
}
void demo_app::update_fps_stats() {
++fps_frame_count_;
auto current_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<f64> elapsed = current_time - fps_timer_;
if (elapsed.count() >= FPS_LOG_INTERVAL) {
current_fps_ = static_cast<f64>(fps_frame_count_) / elapsed.count();
MIRAI_LOG_INFO("FPS: {:.1f} | Frame Time: {:.2f}ms", current_fps_, frame_time_ms_);
fps_frame_count_ = 0;
fps_timer_ = current_time;
}
}
} // namespace mirai::demo

240
src/demo/demo_app.hpp Normal file
View File

@@ -0,0 +1,240 @@
/**
* @file demo_app.hpp
* @brief MIRAI 框架演示应用主类
* @author MIRAI Team
* @version 0.1.0
*
* 演示应用主类,整合窗口管理、渲染器和输入处理。
*/
#pragma once
#include "types/types.hpp"
#include "renderer.hpp"
#include "input_manager.hpp"
#include "logger.hpp"
#include <SDL3/SDL.h>
#include <memory>
#include <string>
#include <chrono>
namespace mirai::demo {
/**
* @brief 演示窗口配置
*/
struct demo_window_config {
/// 窗口宽度
u32 width = 1280;
/// 窗口高度
u32 height = 720;
/// 窗口标题
const char* title = "MIRAI Demo";
/// 是否可调整大小
bool resizable = true;
/// 是否启用 VSync
bool vsync = true;
/// 是否支持高 DPI
bool high_dpi = true;
};
/**
* @brief 演示应用状态
*/
enum class demo_app_state : u8 {
/// 未初始化
uninitialized,
/// 已初始化
initialized,
/// 运行中
running,
/// 已退出
quit
};
/**
* @brief 演示应用主类
*
* 整合窗口管理、渲染器和输入处理的演示应用
*/
class demo_app {
public:
/**
* @brief 构造函数
* @param config 窗口配置
*/
explicit demo_app(const demo_window_config& config = {});
/**
* @brief 析构函数
*/
~demo_app();
// 禁止拷贝和移动
demo_app(const demo_app&) = delete;
demo_app& operator=(const demo_app&) = delete;
demo_app(demo_app&&) = delete;
demo_app& operator=(demo_app&&) = delete;
/**
* @brief 初始化应用
* @return 是否成功
*/
[[nodiscard]] bool initialize();
/**
* @brief 运行主循环
*/
void run();
/**
* @brief 关闭应用
*/
void shutdown();
/**
* @brief 请求退出
*/
void request_quit() noexcept { should_quit_ = true; }
/**
* @brief 检查是否应该退出
* @return 是否应该退出
*/
[[nodiscard]] bool should_quit() const noexcept { return should_quit_; }
/**
* @brief 获取应用状态
* @return 应用状态
*/
[[nodiscard]] demo_app_state get_state() const noexcept { return state_; }
/**
* @brief 获取渲染器
* @return 渲染器指针
*/
[[nodiscard]] renderer* get_renderer() noexcept { return renderer_.get(); }
/**
* @brief 获取输入管理器
* @return 输入管理器指针
*/
[[nodiscard]] input_manager* get_input_manager() noexcept { return input_manager_.get(); }
/**
* @brief 获取窗口
* @return SDL 窗口指针
*/
[[nodiscard]] SDL_Window* get_window() noexcept { return window_; }
/**
* @brief 获取当前帧率
* @return 帧率
*/
[[nodiscard]] f64 get_fps() const noexcept { return current_fps_; }
/**
* @brief 获取帧时间(毫秒)
* @return 帧时间
*/
[[nodiscard]] f64 get_frame_time_ms() const noexcept { return frame_time_ms_; }
private:
/**
* @brief 初始化 SDL
* @return 是否成功
*/
[[nodiscard]] bool init_sdl();
/**
* @brief 创建窗口
* @return 是否成功
*/
[[nodiscard]] bool create_window();
/**
* @brief 初始化渲染器
* @return 是否成功
*/
[[nodiscard]] bool init_renderer();
/**
* @brief 初始化输入管理器
* @return 是否成功
*/
[[nodiscard]] bool init_input();
/**
* @brief 处理 SDL 事件
*/
void handle_events();
/**
* @brief 处理窗口事件
* @param event SDL 事件
*/
void handle_window_event(const SDL_Event& event);
/**
* @brief 更新逻辑
* @param delta_time 帧间隔时间(秒)
*/
void update(f64 delta_time);
/**
* @brief 渲染帧
*/
void render();
/**
* @brief 更新帧率统计
*/
void update_fps_stats();
/// 窗口配置
demo_window_config config_;
/// 应用状态
demo_app_state state_ = demo_app_state::uninitialized;
/// 是否应该退出
bool should_quit_ = false;
/// SDL 窗口
SDL_Window* window_ = nullptr;
/// 渲染器
std::unique_ptr<renderer> renderer_;
/// 输入管理器
std::unique_ptr<input_manager> input_manager_;
/// 帧计数
u64 frame_count_ = 0;
/// 当前帧率
f64 current_fps_ = 0.0;
/// 帧时间(毫秒)
f64 frame_time_ms_ = 0.0;
/// 上一帧时间点
std::chrono::high_resolution_clock::time_point last_frame_time_;
/// FPS 统计时间点
std::chrono::high_resolution_clock::time_point fps_timer_;
/// FPS 统计帧数
u32 fps_frame_count_ = 0;
/// FPS 日志输出间隔(秒)
static constexpr f64 FPS_LOG_INTERVAL = 1.0;
};
} // namespace mirai::demo

View File

@@ -0,0 +1,71 @@
# ================================================================================================
# MIRAI Framework - Demo Widgets 模块
# 描述: 演示用的 Shader Widget 集合
# ================================================================================================
project(mirai_demo_widgets)
# 收集源文件
set(DEMO_WIDGETS_SOURCES
gradient_widget.cpp
blur_widget.cpp
rounded_rect_widget.cpp
particle_widget.cpp
)
set(DEMO_WIDGETS_HEADERS
gradient_widget.hpp
blur_widget.hpp
rounded_rect_widget.hpp
particle_widget.hpp
demo_widgets.hpp
)
# 创建静态库
add_library(${PROJECT_NAME} STATIC
${DEMO_WIDGETS_SOURCES}
${DEMO_WIDGETS_HEADERS}
)
# 设置包含目录(使用生成器表达式处理安装路径)
target_include_directories(${PROJECT_NAME}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/src>
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/src/core>
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/src/render>
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/src/resource>
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/src/shader>
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/src/shader/generated>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
# 链接依赖
target_link_libraries(${PROJECT_NAME} PUBLIC
mirai_core
mirai_render
mirai_resource
mirai_shader
)
# 设置目标属性
set_target_properties(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
EXPORT_NAME demo_widgets
OUTPUT_NAME mirai_demo_widgets
)
# 安装
install(TARGETS ${PROJECT_NAME}
EXPORT mirai-targets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(FILES ${DEMO_WIDGETS_HEADERS}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mirai/demo/widgets
)
message(STATUS "Configured mirai_demo_widgets library")

View File

@@ -0,0 +1,425 @@
/**
* @file blur_widget.cpp
* @brief MIRAI 框架模糊效果 Widget 实现
*/
#include "blur_widget.hpp"
#include "vulkan_device.hpp"
#include "allocator.hpp"
#include "descriptor_pool.hpp"
#include "command_buffer.hpp"
#include "buffer.hpp"
#include "pipeline.hpp"
#include "logger.hpp"
#include <cmath>
#include <algorithm>
namespace mirai {
namespace demo {
// ================================================================================================
// blur_widget 实现
// ================================================================================================
blur_widget::blur_widget() {
// 默认模糊参数
blur_params_.sigma = 5.0f;
blur_params_.samples = 9;
blur_params_.texel_size[0] = 1.0f / 1280.0f;
blur_params_.texel_size[1] = 1.0f / 720.0f;
}
blur_widget::~blur_widget() {
destroy();
}
bool blur_widget::initialize(
std::shared_ptr<vulkan_device> device,
std::shared_ptr<gpu_allocator> allocator,
std::shared_ptr<descriptor_pool> pool,
vk::Format color_format) {
if (initialized_) {
MIRAI_LOG_WARN("blur_widget already initialized");
return true;
}
device_ = std::move(device);
allocator_ = std::move(allocator);
pool_ = std::move(pool);
color_format_ = color_format;
// 创建资源
if (!create_uniform_buffers()) {
MIRAI_LOG_ERROR("Failed to create uniform buffers for blur_widget");
return false;
}
if (!create_descriptor_sets()) {
MIRAI_LOG_ERROR("Failed to create descriptor sets for blur_widget");
return false;
}
if (!create_pipeline()) {
MIRAI_LOG_ERROR("Failed to create pipeline for blur_widget");
return false;
}
initialized_ = true;
MIRAI_LOG_INFO("blur_widget initialized successfully");
return true;
}
void blur_widget::destroy() {
if (!initialized_) {
return;
}
// 等待设备空闲
if (device_) {
device_->get_device().waitIdle();
}
// 销毁描述符集布局
if (set0_layout_ && device_) {
device_->get_device().destroyDescriptorSetLayout(set0_layout_);
set0_layout_ = nullptr;
}
if (set1_layout_ && device_) {
device_->get_device().destroyDescriptorSetLayout(set1_layout_);
set1_layout_ = nullptr;
}
// 销毁管线
pipeline_.reset();
pipeline_layout_.reset();
// 销毁缓冲区
bounds_ubo_.reset();
params_ubo_.reset();
vertex_buffer_.reset();
// 清理引用
device_.reset();
allocator_.reset();
pool_.reset();
initialized_ = false;
MIRAI_LOG_INFO("blur_widget destroyed");
}
void blur_widget::set_bounds(f32 x, f32 y, f32 width, f32 height) {
bounds_.bounds[0] = x;
bounds_.bounds[1] = y;
bounds_.bounds[2] = width;
bounds_.bounds[3] = height;
bounds_dirty_ = true;
}
void blur_widget::set_opacity(f32 opacity) {
bounds_.opacity = std::clamp(opacity, 0.0f, 1.0f);
bounds_dirty_ = true;
}
void blur_widget::set_blur_params(f32 sigma, i32 samples) {
blur_params_.sigma = std::max(0.1f, sigma);
blur_params_.samples = std::clamp(samples, 1, 31);
params_dirty_ = true;
}
void blur_widget::set_texture_size(u32 width, u32 height) {
blur_params_.texel_size[0] = 1.0f / static_cast<f32>(width);
blur_params_.texel_size[1] = 1.0f / static_cast<f32>(height);
params_dirty_ = true;
}
void blur_widget::set_backdrop_texture(vk::ImageView view, vk::Sampler samp) {
backdrop_view_ = view;
backdrop_sampler_ = samp;
texture_dirty_ = true;
}
void blur_widget::update(command_buffer& /*cmd*/) {
if (!initialized_) {
return;
}
// 更新 bounds UBO
if (bounds_dirty_ && bounds_ubo_) {
bounds_ubo_->write(&bounds_, sizeof(bounds_), 0);
bounds_dirty_ = false;
}
// 更新 blur params UBO
if (params_dirty_ && params_ubo_) {
params_ubo_->write(&blur_params_, sizeof(blur_params_), 0);
params_dirty_ = false;
}
// 更新纹理描述符
if (texture_dirty_) {
update_texture_descriptor();
texture_dirty_ = false;
}
}
void blur_widget::render(command_buffer& cmd) {
if (!initialized_ || !pipeline_ || !pipeline_->is_valid()) {
return;
}
if (!backdrop_view_ || !backdrop_sampler_) {
MIRAI_LOG_WARN("blur_widget: No backdrop texture set");
return;
}
auto vk_cmd = cmd.get_vulkan_buffer();
auto vk_pipeline = pipeline_->get_vulkan_pipeline();
auto vk_layout = pipeline_->get_layout();
// 绑定管线
vk_cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, vk_pipeline);
// 绑定描述符集
std::array<vk::DescriptorSet, 2> sets = {set0_, set1_};
vk_cmd.bindDescriptorSets(
vk::PipelineBindPoint::eGraphics,
vk_layout,
0,
static_cast<u32>(sets.size()),
sets.data(),
0,
nullptr
);
// 绘制全屏四边形
vk_cmd.draw(6, 1, 0, 0);
}
bool blur_widget::create_pipeline() {
auto vk_device = device_->get_device();
// 获取着色器 SPIR-V
auto vert_spirv = shaders::quad::vertex::spirv();
auto frag_spirv = shaders::blur::fragment::spirv();
if (vert_spirv.empty() || frag_spirv.empty()) {
MIRAI_LOG_ERROR("Failed to get shader SPIR-V data");
return false;
}
// 创建着色器模块
vk::ShaderModuleCreateInfo vert_info{{}, vert_spirv.size() * sizeof(u32), vert_spirv.data()};
auto [vert_result, vert_module] = vk_device.createShaderModule(vert_info);
if (vert_result != vk::Result::eSuccess) {
MIRAI_LOG_ERROR("Failed to create vertex shader module");
return false;
}
vk::ShaderModuleCreateInfo frag_info{{}, frag_spirv.size() * sizeof(u32), frag_spirv.data()};
auto [frag_result, frag_module] = vk_device.createShaderModule(frag_info);
if (frag_result != vk::Result::eSuccess) {
vk_device.destroyShaderModule(vert_module);
MIRAI_LOG_ERROR("Failed to create fragment shader module");
return false;
}
// 创建管线布局
std::array<vk::DescriptorSetLayout, 2> layouts = {set0_layout_, set1_layout_};
pipeline_layout_config layout_config;
layout_config.descriptor_set_layouts = {layouts.begin(), layouts.end()};
pipeline_layout_ = std::make_unique<pipeline_layout>(device_, layout_config);
if (!pipeline_layout_->is_valid()) {
vk_device.destroyShaderModule(vert_module);
vk_device.destroyShaderModule(frag_module);
MIRAI_LOG_ERROR("Failed to create pipeline layout");
return false;
}
// 使用构建器创建管线
graphics_pipeline_builder builder(device_);
pipeline_ = builder
.set_vertex_shader(vert_module, "main")
.set_fragment_shader(frag_module, "main")
.set_topology(vk::PrimitiveTopology::eTriangleList)
.set_polygon_mode(vk::PolygonMode::eFill)
.set_cull_mode(vk::CullModeFlagBits::eNone)
.set_front_face(vk::FrontFace::eCounterClockwise)
.enable_depth_test(false)
.enable_depth_write(false)
.enable_blending(true)
.set_blend_factors(
vk::BlendFactor::eSrcAlpha,
vk::BlendFactor::eOneMinusSrcAlpha,
vk::BlendFactor::eOne,
vk::BlendFactor::eOneMinusSrcAlpha)
.set_color_format(color_format_)
.add_dynamic_state(vk::DynamicState::eViewport)
.add_dynamic_state(vk::DynamicState::eScissor)
.set_layout(*pipeline_layout_)
.build();
// 销毁着色器模块(管线创建后不再需要)
vk_device.destroyShaderModule(vert_module);
vk_device.destroyShaderModule(frag_module);
if (!pipeline_ || !pipeline_->is_valid()) {
MIRAI_LOG_ERROR("Failed to create graphics pipeline");
return false;
}
return true;
}
bool blur_widget::create_descriptor_sets() {
auto vk_device = device_->get_device();
// 创建 Set 0 布局: bounds UBO (binding 0) + backdrop sampler (binding 1)
std::array<vk::DescriptorSetLayoutBinding, 2> set0_bindings = {{
{0, vk::DescriptorType::eUniformBuffer, 1,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, nullptr},
{1, vk::DescriptorType::eCombinedImageSampler, 1,
vk::ShaderStageFlagBits::eFragment, nullptr}
}};
vk::DescriptorSetLayoutCreateInfo set0_layout_info{{},
static_cast<u32>(set0_bindings.size()), set0_bindings.data()};
auto [set0_result, set0_layout] = vk_device.createDescriptorSetLayout(set0_layout_info);
if (set0_result != vk::Result::eSuccess) {
MIRAI_LOG_ERROR("Failed to create set 0 descriptor set layout");
return false;
}
set0_layout_ = set0_layout;
// 创建 Set 1 布局: blur params UBO (binding 0)
vk::DescriptorSetLayoutBinding set1_binding{
0, vk::DescriptorType::eUniformBuffer, 1,
vk::ShaderStageFlagBits::eFragment, nullptr
};
vk::DescriptorSetLayoutCreateInfo set1_layout_info{{}, 1, &set1_binding};
auto [set1_result, set1_layout] = vk_device.createDescriptorSetLayout(set1_layout_info);
if (set1_result != vk::Result::eSuccess) {
MIRAI_LOG_ERROR("Failed to create set 1 descriptor set layout");
return false;
}
set1_layout_ = set1_layout;
// 分配描述符集 0
auto set0_alloc_result = pool_->allocate(set0_layout_);
if (!set0_alloc_result) {
MIRAI_LOG_ERROR("Failed to allocate descriptor set 0");
return false;
}
set0_ = set0_alloc_result.value()->get_vulkan_set();
// 分配描述符集 1
auto set1_alloc_result = pool_->allocate(set1_layout_);
if (!set1_alloc_result) {
MIRAI_LOG_ERROR("Failed to allocate descriptor set 1");
return false;
}
set1_ = set1_alloc_result.value()->get_vulkan_set();
// 更新描述符集 - UBO 部分
vk::DescriptorBufferInfo bounds_buffer_info{
bounds_ubo_->get_vulkan_buffer(),
0,
sizeof(blur_widget_bounds_ubo)
};
vk::DescriptorBufferInfo params_buffer_info{
params_ubo_->get_vulkan_buffer(),
0,
sizeof(blur_params_ubo)
};
std::array<vk::WriteDescriptorSet, 2> writes = {{
{set0_, 0, 0, 1, vk::DescriptorType::eUniformBuffer, nullptr, &bounds_buffer_info, nullptr},
{set1_, 0, 0, 1, vk::DescriptorType::eUniformBuffer, nullptr, &params_buffer_info, nullptr}
}};
vk_device.updateDescriptorSets(static_cast<u32>(writes.size()), writes.data(), 0, nullptr);
return true;
}
bool blur_widget::create_vertex_buffer() {
// 不需要顶点缓冲区,使用 gl_VertexIndex 生成顶点
return true;
}
bool blur_widget::create_uniform_buffers() {
// 创建 bounds Uniform Buffer
{
buffer_create_info info;
info.size = sizeof(blur_widget_bounds_ubo);
info.usage = buffer_usage::uniform;
info.mem_usage = memory_usage::cpu_to_gpu;
info.persistent_mapped = true;
info.debug_name = "blur_widget_bounds_ubo";
bounds_ubo_ = std::make_unique<buffer>(allocator_, info);
if (!bounds_ubo_->is_valid()) {
MIRAI_LOG_ERROR("Failed to create bounds uniform buffer");
return false;
}
bounds_ubo_->write(&bounds_, sizeof(bounds_), 0);
}
// 创建 blur params Uniform Buffer
{
buffer_create_info info;
info.size = sizeof(blur_params_ubo);
info.usage = buffer_usage::uniform;
info.mem_usage = memory_usage::cpu_to_gpu;
info.persistent_mapped = true;
info.debug_name = "blur_widget_params_ubo";
params_ubo_ = std::make_unique<buffer>(allocator_, info);
if (!params_ubo_->is_valid()) {
MIRAI_LOG_ERROR("Failed to create params uniform buffer");
return false;
}
params_ubo_->write(&blur_params_, sizeof(blur_params_), 0);
}
return true;
}
void blur_widget::update_texture_descriptor() {
if (!backdrop_view_ || !backdrop_sampler_ || !set0_) {
return;
}
auto vk_device = device_->get_device();
vk::DescriptorImageInfo image_info{
backdrop_sampler_,
backdrop_view_,
vk::ImageLayout::eShaderReadOnlyOptimal
};
vk::WriteDescriptorSet write{
set0_,
1,
0,
1,
vk::DescriptorType::eCombinedImageSampler,
&image_info,
nullptr,
nullptr
};
vk_device.updateDescriptorSets(1, &write, 0, nullptr);
}
} // namespace demo
} // namespace mirai

View File

@@ -0,0 +1,329 @@
/**
* @file blur_widget.hpp
* @brief MIRAI 框架模糊效果 Widget
* @author MIRAI Team
* @version 0.1.0
*
* 使用 Backdrop 模式实现高斯模糊效果。
* 使用 quad.vert + blur.frag 着色器。
*/
#pragma once
#include "types/types.hpp"
#include "vulkan_types.hpp"
#include "object.hpp"
#include <memory>
#include <array>
namespace mirai {
// 前向声明
class vulkan_device;
class gpu_allocator;
class descriptor_pool;
class command_buffer;
class buffer;
class graphics_pipeline;
class pipeline_layout;
class texture_view;
class sampler;
namespace demo {
// ================================================================================================
// UBO 结构
// ================================================================================================
/**
* @brief Widget 边界 UBO 结构Set 0, Binding 0
*/
struct alignas(16) blur_widget_bounds_ubo {
/// 边界 (x, y, width, height) - 归一化屏幕坐标
std::array<f32, 4> bounds = {0.0f, 0.0f, 1.0f, 1.0f};
/// 透明度
f32 opacity = 1.0f;
/// 填充
f32 _pad[3] = {0.0f, 0.0f, 0.0f};
};
static_assert(sizeof(blur_widget_bounds_ubo) == 32, "blur_widget_bounds_ubo size mismatch");
/**
* @brief 模糊参数 UBO 结构Set 1, Binding 0
*
* 与 blur.frag 中的 BlurParams 布局匹配
*/
struct alignas(16) blur_params_ubo {
/// 高斯分布标准差
f32 sigma = 2.0f;
/// 采样半径(实际采样数为 (2*samples+1)^2
i32 samples = 3;
/// 纹理像素大小 (1.0/width, 1.0/height)
std::array<f32, 2> texel_size = {1.0f / 1280.0f, 1.0f / 720.0f};
};
static_assert(sizeof(blur_params_ubo) == 16, "blur_params_ubo size mismatch");
// ================================================================================================
// blur_widget 类
// ================================================================================================
/**
* @brief 模糊效果 Widget
*
* 使用 Backdrop 模式实现高斯模糊效果。
* 需要提供背景纹理进行采样。
*
* @example
* @code
* auto widget = std::make_unique<blur_widget>();
* widget->initialize(device, allocator, pool);
* widget->set_bounds(0.1f, 0.1f, 0.3f, 0.3f);
* widget->set_blur_params(3.0f, 5);
* widget->set_backdrop_texture(backdrop_view, backdrop_sampler);
*
* // 在渲染循环中
* widget->update(cmd);
* widget->render(cmd);
* @endcode
*/
class blur_widget : public object {
public:
MIRAI_OBJECT_TYPE_INFO(blur_widget, object)
/**
* @brief 默认构造函数
*/
blur_widget();
/**
* @brief 析构函数
*/
~blur_widget() override;
// 禁止拷贝和移动
blur_widget(const blur_widget&) = delete;
blur_widget& operator=(const blur_widget&) = delete;
blur_widget(blur_widget&&) = delete;
blur_widget& operator=(blur_widget&&) = delete;
// ============================================================================================
// 生命周期
// ============================================================================================
/**
* @brief 初始化 Widget
* @param device Vulkan 设备
* @param allocator GPU 内存分配器
* @param pool 描述符池
* @param color_format 颜色附件格式
* @return 是否成功
*/
[[nodiscard]] bool initialize(
std::shared_ptr<vulkan_device> device,
std::shared_ptr<gpu_allocator> allocator,
std::shared_ptr<descriptor_pool> pool,
vk::Format color_format = vk::Format::eB8G8R8A8Srgb);
/**
* @brief 销毁 Widget 资源
*/
void destroy();
/**
* @brief 检查是否已初始化
* @return 是否已初始化
*/
[[nodiscard]] bool is_initialized() const noexcept { return initialized_; }
// ============================================================================================
// 参数设置
// ============================================================================================
/**
* @brief 设置 Widget 边界
* @param x X 坐标(归一化 0-1
* @param y Y 坐标(归一化 0-1
* @param width 宽度(归一化 0-1
* @param height 高度(归一化 0-1
*/
void set_bounds(f32 x, f32 y, f32 width, f32 height);
/**
* @brief 设置透明度
* @param opacity 透明度 (0-1)
*/
void set_opacity(f32 opacity);
/**
* @brief 设置模糊参数
* @param sigma 高斯分布标准差
* @param samples 采样半径
*/
void set_blur_params(f32 sigma, i32 samples);
/**
* @brief 设置纹理像素大小
* @param width 纹理宽度
* @param height 纹理高度
*/
void set_texture_size(u32 width, u32 height);
/**
* @brief 设置背景纹理
* @param view 纹理视图
* @param samp 采样器
*/
void set_backdrop_texture(vk::ImageView view, vk::Sampler samp);
// ============================================================================================
// 渲染
// ============================================================================================
/**
* @brief 更新 Uniform Buffer
* @param cmd 命令缓冲区
*/
void update(command_buffer& cmd);
/**
* @brief 渲染 Widget
* @param cmd 命令缓冲区
*/
void render(command_buffer& cmd);
// ============================================================================================
// 属性访问
// ============================================================================================
/**
* @brief 获取 X 坐标
*/
[[nodiscard]] f32 x() const noexcept { return bounds_.bounds[0]; }
/**
* @brief 获取 Y 坐标
*/
[[nodiscard]] f32 y() const noexcept { return bounds_.bounds[1]; }
/**
* @brief 获取宽度
*/
[[nodiscard]] f32 width() const noexcept { return bounds_.bounds[2]; }
/**
* @brief 获取高度
*/
[[nodiscard]] f32 height() const noexcept { return bounds_.bounds[3]; }
/**
* @brief 获取透明度
*/
[[nodiscard]] f32 opacity() const noexcept { return bounds_.opacity; }
/**
* @brief 获取模糊 sigma
*/
[[nodiscard]] f32 sigma() const noexcept { return blur_params_.sigma; }
/**
* @brief 获取采样半径
*/
[[nodiscard]] i32 samples() const noexcept { return blur_params_.samples; }
private:
/**
* @brief 创建管线
* @return 是否成功
*/
[[nodiscard]] bool create_pipeline();
/**
* @brief 创建描述符集
* @return 是否成功
*/
[[nodiscard]] bool create_descriptor_sets();
/**
* @brief 创建顶点缓冲区
* @return 是否成功
*/
[[nodiscard]] bool create_vertex_buffer();
/**
* @brief 创建 Uniform Buffer
* @return 是否成功
*/
[[nodiscard]] bool create_uniform_buffers();
/**
* @brief 更新描述符集中的纹理绑定
*/
void update_texture_descriptor();
/// 是否已初始化
bool initialized_ = false;
/// 是否需要更新 bounds UBO
bool bounds_dirty_ = true;
/// 是否需要更新 blur params UBO
bool params_dirty_ = true;
/// 是否需要更新纹理描述符
bool texture_dirty_ = false;
/// Vulkan 设备
std::shared_ptr<vulkan_device> device_;
/// GPU 分配器
std::shared_ptr<gpu_allocator> allocator_;
/// 描述符池
std::shared_ptr<descriptor_pool> pool_;
/// 颜色格式
vk::Format color_format_ = vk::Format::eB8G8R8A8Srgb;
/// 管线布局
std::unique_ptr<pipeline_layout> pipeline_layout_;
/// 图形管线
std::unique_ptr<graphics_pipeline> pipeline_;
/// 描述符集布局 (Set 0 - 框架)
vk::DescriptorSetLayout set0_layout_;
/// 描述符集布局 (Set 1 - 用户 UBO)
vk::DescriptorSetLayout set1_layout_;
/// 描述符集 (Set 0)
vk::DescriptorSet set0_;
/// 描述符集 (Set 1)
vk::DescriptorSet set1_;
/// 顶点缓冲区
std::unique_ptr<buffer> vertex_buffer_;
/// Uniform Buffer (Widget Bounds - Set 0)
std::unique_ptr<buffer> bounds_ubo_;
/// Uniform Buffer (Blur Params - Set 1)
std::unique_ptr<buffer> params_ubo_;
/// Widget 边界数据
blur_widget_bounds_ubo bounds_;
/// 模糊参数数据
blur_params_ubo blur_params_;
/// 背景纹理视图
vk::ImageView backdrop_view_;
/// 背景采样器
vk::Sampler backdrop_sampler_;
};
} // namespace demo
} // namespace mirai

View File

@@ -0,0 +1,56 @@
/**
* @file demo_widgets.hpp
* @brief MIRAI 框架演示 Widget 统一头文件
* @author MIRAI Team
* @version 0.1.0
*
* 包含所有演示用的 Shader Widget。
*/
#pragma once
// ============================================================================
// 演示 Widget
// ============================================================================
// 渐变效果 Widget (Procedural 模式)
// 使用 quad.vert + gradient.frag
// 支持线性渐变,通过 Push Constant 设置颜色和角度
#include "gradient_widget.hpp"
// 模糊效果 Widget (Backdrop 模式)
// 使用 quad.vert + blur.frag
// 支持高斯模糊,需要背景纹理采样
#include "blur_widget.hpp"
// 圆角矩形 Widget (Mask 模式)
// 使用 quad.vert + rounded_rect.frag
// 使用 SDF 实现高质量圆角
#include "rounded_rect_widget.hpp"
// 粒子系统 Widget (Custom Mesh 模式)
// 使用自定义顶点着色器和片段着色器
// 支持实例化渲染
#include "particle_widget.hpp"
// ============================================================================
// 便捷类型别名
// ============================================================================
namespace mirai::demo {
/**
* @brief Widget 类型枚举
*/
enum class demo_widget_type {
/// 渐变效果
gradient,
/// 模糊效果
blur,
/// 圆角矩形
rounded_rect,
/// 粒子系统
particle
};
} // namespace mirai::demo

View File

@@ -0,0 +1,351 @@
/**
* @file gradient_widget.cpp
* @brief MIRAI 框架渐变效果 Widget 实现
*/
#include "gradient_widget.hpp"
#include "vulkan_device.hpp"
#include "allocator.hpp"
#include "descriptor_pool.hpp"
#include "command_buffer.hpp"
#include "buffer.hpp"
#include "pipeline.hpp"
#include "logger.hpp"
#include <cmath>
#include <algorithm>
namespace mirai {
namespace demo {
// ================================================================================================
// gradient_widget 实现
// ================================================================================================
gradient_widget::gradient_widget() {
// 默认渐变:从蓝色到紫色
push_constants_.color_start[0] = 0.2f;
push_constants_.color_start[1] = 0.4f;
push_constants_.color_start[2] = 0.8f;
push_constants_.color_start[3] = 1.0f;
push_constants_.color_end[0] = 0.8f;
push_constants_.color_end[1] = 0.2f;
push_constants_.color_end[2] = 0.6f;
push_constants_.color_end[3] = 1.0f;
push_constants_.angle = 0.0f;
}
gradient_widget::~gradient_widget() {
destroy();
}
bool gradient_widget::initialize(
std::shared_ptr<vulkan_device> device,
std::shared_ptr<gpu_allocator> allocator,
std::shared_ptr<descriptor_pool> pool,
vk::Format color_format) {
if (initialized_) {
MIRAI_LOG_WARN("gradient_widget already initialized");
return true;
}
device_ = std::move(device);
allocator_ = std::move(allocator);
pool_ = std::move(pool);
color_format_ = color_format;
// 创建资源
if (!create_uniform_buffer()) {
MIRAI_LOG_ERROR("Failed to create uniform buffer for gradient_widget");
return false;
}
if (!create_descriptor_sets()) {
MIRAI_LOG_ERROR("Failed to create descriptor sets for gradient_widget");
return false;
}
if (!create_pipeline()) {
MIRAI_LOG_ERROR("Failed to create pipeline for gradient_widget");
return false;
}
initialized_ = true;
MIRAI_LOG_INFO("gradient_widget initialized successfully");
return true;
}
void gradient_widget::destroy() {
if (!initialized_) {
return;
}
// 等待设备空闲
if (device_) {
device_->get_device().waitIdle();
}
// 销毁描述符集布局
if (descriptor_set_layout_ && device_) {
device_->get_device().destroyDescriptorSetLayout(descriptor_set_layout_);
descriptor_set_layout_ = nullptr;
}
// 销毁管线
pipeline_.reset();
pipeline_layout_.reset();
// 销毁缓冲区
uniform_buffer_.reset();
vertex_buffer_.reset();
// 清理引用
device_.reset();
allocator_.reset();
pool_.reset();
initialized_ = false;
MIRAI_LOG_INFO("gradient_widget destroyed");
}
void gradient_widget::set_bounds(f32 x, f32 y, f32 width, f32 height) {
bounds_.bounds[0] = x;
bounds_.bounds[1] = y;
bounds_.bounds[2] = width;
bounds_.bounds[3] = height;
ubo_dirty_ = true;
}
void gradient_widget::set_opacity(f32 opacity) {
bounds_.opacity = std::clamp(opacity, 0.0f, 1.0f);
ubo_dirty_ = true;
}
void gradient_widget::set_colors(const std::array<f32, 4>& start, const std::array<f32, 4>& end) {
push_constants_.color_start = start;
push_constants_.color_end = end;
}
void gradient_widget::set_angle(f32 angle_radians) {
push_constants_.angle = angle_radians;
}
void gradient_widget::set_angle_degrees(f32 angle_degrees) {
push_constants_.angle = angle_degrees * 3.14159265358979f / 180.0f;
}
void gradient_widget::update(command_buffer& /*cmd*/) {
if (!initialized_) {
return;
}
// 更新 UBO
if (ubo_dirty_ && uniform_buffer_) {
uniform_buffer_->write(&bounds_, sizeof(bounds_), 0);
ubo_dirty_ = false;
}
}
void gradient_widget::render(command_buffer& cmd) {
if (!initialized_ || !pipeline_ || !pipeline_->is_valid()) {
return;
}
auto vk_cmd = cmd.get_vulkan_buffer();
auto vk_pipeline = pipeline_->get_vulkan_pipeline();
auto vk_layout = pipeline_->get_layout();
// 绑定管线
vk_cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, vk_pipeline);
// 绑定描述符集
vk_cmd.bindDescriptorSets(
vk::PipelineBindPoint::eGraphics,
vk_layout,
0,
1,
&descriptor_set_,
0,
nullptr
);
// 推送常量
vk_cmd.pushConstants(
vk_layout,
vk::ShaderStageFlagBits::eFragment,
0,
sizeof(push_constants_),
&push_constants_
);
// 绘制全屏四边形(使用顶点着色器生成顶点)
vk_cmd.draw(6, 1, 0, 0);
}
bool gradient_widget::create_pipeline() {
auto vk_device = device_->get_device();
// 获取着色器 SPIR-V
auto vert_spirv = shaders::quad::vertex::spirv();
auto frag_spirv = shaders::gradient::fragment::spirv();
if (vert_spirv.empty() || frag_spirv.empty()) {
MIRAI_LOG_ERROR("Failed to get shader SPIR-V data");
return false;
}
// 创建着色器模块
vk::ShaderModuleCreateInfo vert_info{{}, vert_spirv.size() * sizeof(u32), vert_spirv.data()};
auto [vert_result, vert_module] = vk_device.createShaderModule(vert_info);
if (vert_result != vk::Result::eSuccess) {
MIRAI_LOG_ERROR("Failed to create vertex shader module");
return false;
}
vk::ShaderModuleCreateInfo frag_info{{}, frag_spirv.size() * sizeof(u32), frag_spirv.data()};
auto [frag_result, frag_module] = vk_device.createShaderModule(frag_info);
if (frag_result != vk::Result::eSuccess) {
vk_device.destroyShaderModule(vert_module);
MIRAI_LOG_ERROR("Failed to create fragment shader module");
return false;
}
// 创建管线布局
vk::PushConstantRange push_constant_range{
vk::ShaderStageFlagBits::eFragment,
0,
sizeof(gradient_push_constants)
};
pipeline_layout_config layout_config;
layout_config.descriptor_set_layouts = {descriptor_set_layout_};
layout_config.push_constant_ranges = {push_constant_range};
pipeline_layout_ = std::make_unique<pipeline_layout>(device_, layout_config);
if (!pipeline_layout_->is_valid()) {
vk_device.destroyShaderModule(vert_module);
vk_device.destroyShaderModule(frag_module);
MIRAI_LOG_ERROR("Failed to create pipeline layout");
return false;
}
// 使用构建器创建管线
graphics_pipeline_builder builder(device_);
pipeline_ = builder
.set_vertex_shader(vert_module, "main")
.set_fragment_shader(frag_module, "main")
// 不需要顶点输入,使用 gl_VertexIndex 生成顶点
.set_topology(vk::PrimitiveTopology::eTriangleList)
.set_polygon_mode(vk::PolygonMode::eFill)
.set_cull_mode(vk::CullModeFlagBits::eNone)
.set_front_face(vk::FrontFace::eCounterClockwise)
.enable_depth_test(false)
.enable_depth_write(false)
.enable_blending(true)
.set_blend_factors(
vk::BlendFactor::eSrcAlpha,
vk::BlendFactor::eOneMinusSrcAlpha,
vk::BlendFactor::eOne,
vk::BlendFactor::eOneMinusSrcAlpha)
.set_color_format(color_format_)
.add_dynamic_state(vk::DynamicState::eViewport)
.add_dynamic_state(vk::DynamicState::eScissor)
.set_layout(*pipeline_layout_)
.build();
// 销毁着色器模块(管线创建后不再需要)
vk_device.destroyShaderModule(vert_module);
vk_device.destroyShaderModule(frag_module);
if (!pipeline_ || !pipeline_->is_valid()) {
MIRAI_LOG_ERROR("Failed to create graphics pipeline");
return false;
}
return true;
}
bool gradient_widget::create_descriptor_sets() {
auto vk_device = device_->get_device();
// 创建描述符集布局
vk::DescriptorSetLayoutBinding binding{
0,
vk::DescriptorType::eUniformBuffer,
1,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
nullptr
};
vk::DescriptorSetLayoutCreateInfo layout_info{{}, 1, &binding};
auto [layout_result, layout] = vk_device.createDescriptorSetLayout(layout_info);
if (layout_result != vk::Result::eSuccess) {
MIRAI_LOG_ERROR("Failed to create descriptor set layout");
return false;
}
descriptor_set_layout_ = layout;
// 分配描述符集
auto set_result = pool_->allocate(descriptor_set_layout_);
if (!set_result) {
MIRAI_LOG_ERROR("Failed to allocate descriptor set");
return false;
}
descriptor_set_ = set_result.value()->get_vulkan_set();
// 更新描述符集
vk::DescriptorBufferInfo buffer_info{
uniform_buffer_->get_vulkan_buffer(),
0,
sizeof(widget_bounds_ubo)
};
vk::WriteDescriptorSet write{
descriptor_set_,
0,
0,
1,
vk::DescriptorType::eUniformBuffer,
nullptr,
&buffer_info,
nullptr
};
vk_device.updateDescriptorSets(1, &write, 0, nullptr);
return true;
}
bool gradient_widget::create_vertex_buffer() {
// 不需要顶点缓冲区,使用 gl_VertexIndex 生成顶点
return true;
}
bool gradient_widget::create_uniform_buffer() {
buffer_create_info info;
info.size = sizeof(widget_bounds_ubo);
info.usage = buffer_usage::uniform;
info.mem_usage = memory_usage::cpu_to_gpu;
info.persistent_mapped = true;
info.debug_name = "gradient_widget_uniform_buffer";
uniform_buffer_ = std::make_unique<buffer>(allocator_, info);
if (!uniform_buffer_->is_valid()) {
MIRAI_LOG_ERROR("Failed to create uniform buffer");
return false;
}
// 初始化 UBO 数据
uniform_buffer_->write(&bounds_, sizeof(bounds_), 0);
return true;
}
} // namespace demo
} // namespace mirai

View File

@@ -0,0 +1,301 @@
/**
* @file gradient_widget.hpp
* @brief MIRAI 框架渐变效果 Widget
* @author MIRAI Team
* @version 0.1.0
*
* 使用 Procedural 模式实现线性渐变效果。
* 使用 quad.vert + gradient.frag 着色器。
*/
#pragma once
#include "types/types.hpp"
#include "vulkan_types.hpp"
#include "object.hpp"
#include <memory>
#include <array>
namespace mirai {
// 前向声明
class vulkan_device;
class gpu_allocator;
class descriptor_pool;
class command_buffer;
class buffer;
class graphics_pipeline;
class pipeline_layout;
namespace demo {
// ================================================================================================
// Push Constant 结构
// ================================================================================================
/**
* @brief 渐变效果 Push Constant 结构
*
* 与 gradient.frag 中的 PushConstants 布局匹配
*/
struct alignas(16) gradient_push_constants {
/// 起始颜色 (RGBA)
std::array<f32, 4> color_start = {1.0f, 0.0f, 0.0f, 1.0f};
/// 结束颜色 (RGBA)
std::array<f32, 4> color_end = {0.0f, 0.0f, 1.0f, 1.0f};
/// 渐变角度(弧度)
f32 angle = 0.0f;
/// 填充对齐
f32 _pad[3] = {0.0f, 0.0f, 0.0f};
};
static_assert(sizeof(gradient_push_constants) == 48, "gradient_push_constants size mismatch");
// ================================================================================================
// Widget Bounds UBO 结构
// ================================================================================================
/**
* @brief Widget 边界 UBO 结构
*
* 与着色器中的 WidgetBounds 布局匹配
*/
struct alignas(16) widget_bounds_ubo {
/// 边界 (x, y, width, height) - 归一化屏幕坐标
std::array<f32, 4> bounds = {0.0f, 0.0f, 1.0f, 1.0f};
/// 透明度
f32 opacity = 1.0f;
/// 填充
f32 _pad[3] = {0.0f, 0.0f, 0.0f};
};
static_assert(sizeof(widget_bounds_ubo) == 32, "widget_bounds_ubo size mismatch");
// ================================================================================================
// gradient_widget 类
// ================================================================================================
/**
* @brief 渐变效果 Widget
*
* 使用 Procedural 模式实现线性渐变效果。
*
* @example
* @code
* auto widget = std::make_unique<gradient_widget>();
* widget->initialize(device, allocator, pool);
* widget->set_bounds(0.1f, 0.1f, 0.3f, 0.3f);
* widget->set_colors({1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f, 1.0f});
* widget->set_angle(0.785f); // 45度
*
* // 在渲染循环中
* widget->update(cmd);
* widget->render(cmd);
* @endcode
*/
class gradient_widget : public object {
public:
MIRAI_OBJECT_TYPE_INFO(gradient_widget, object)
/**
* @brief 默认构造函数
*/
gradient_widget();
/**
* @brief 析构函数
*/
~gradient_widget() override;
// 禁止拷贝和移动
gradient_widget(const gradient_widget&) = delete;
gradient_widget& operator=(const gradient_widget&) = delete;
gradient_widget(gradient_widget&&) = delete;
gradient_widget& operator=(gradient_widget&&) = delete;
// ============================================================================================
// 生命周期
// ============================================================================================
/**
* @brief 初始化 Widget
* @param device Vulkan 设备
* @param allocator GPU 内存分配器
* @param pool 描述符池
* @param color_format 颜色附件格式
* @return 是否成功
*/
[[nodiscard]] bool initialize(
std::shared_ptr<vulkan_device> device,
std::shared_ptr<gpu_allocator> allocator,
std::shared_ptr<descriptor_pool> pool,
vk::Format color_format = vk::Format::eB8G8R8A8Srgb);
/**
* @brief 销毁 Widget 资源
*/
void destroy();
/**
* @brief 检查是否已初始化
* @return 是否已初始化
*/
[[nodiscard]] bool is_initialized() const noexcept { return initialized_; }
// ============================================================================================
// 参数设置
// ============================================================================================
/**
* @brief 设置 Widget 边界
* @param x X 坐标(归一化 0-1
* @param y Y 坐标(归一化 0-1
* @param width 宽度(归一化 0-1
* @param height 高度(归一化 0-1
*/
void set_bounds(f32 x, f32 y, f32 width, f32 height);
/**
* @brief 设置透明度
* @param opacity 透明度 (0-1)
*/
void set_opacity(f32 opacity);
/**
* @brief 设置渐变颜色
* @param start 起始颜色 (RGBA)
* @param end 结束颜色 (RGBA)
*/
void set_colors(const std::array<f32, 4>& start, const std::array<f32, 4>& end);
/**
* @brief 设置渐变角度
* @param angle_radians 角度(弧度)
*/
void set_angle(f32 angle_radians);
/**
* @brief 设置渐变角度(度数)
* @param angle_degrees 角度(度)
*/
void set_angle_degrees(f32 angle_degrees);
// ============================================================================================
// 渲染
// ============================================================================================
/**
* @brief 更新 Uniform Buffer
* @param cmd 命令缓冲区
*/
void update(command_buffer& cmd);
/**
* @brief 渲染 Widget
* @param cmd 命令缓冲区
*/
void render(command_buffer& cmd);
// ============================================================================================
// 属性访问
// ============================================================================================
/**
* @brief 获取 X 坐标
*/
[[nodiscard]] f32 x() const noexcept { return bounds_.bounds[0]; }
/**
* @brief 获取 Y 坐标
*/
[[nodiscard]] f32 y() const noexcept { return bounds_.bounds[1]; }
/**
* @brief 获取宽度
*/
[[nodiscard]] f32 width() const noexcept { return bounds_.bounds[2]; }
/**
* @brief 获取高度
*/
[[nodiscard]] f32 height() const noexcept { return bounds_.bounds[3]; }
/**
* @brief 获取透明度
*/
[[nodiscard]] f32 opacity() const noexcept { return bounds_.opacity; }
/**
* @brief 获取渐变角度
*/
[[nodiscard]] f32 angle() const noexcept { return push_constants_.angle; }
private:
/**
* @brief 创建管线
* @return 是否成功
*/
[[nodiscard]] bool create_pipeline();
/**
* @brief 创建描述符集
* @return 是否成功
*/
[[nodiscard]] bool create_descriptor_sets();
/**
* @brief 创建顶点缓冲区
* @return 是否成功
*/
[[nodiscard]] bool create_vertex_buffer();
/**
* @brief 创建 Uniform Buffer
* @return 是否成功
*/
[[nodiscard]] bool create_uniform_buffer();
/// 是否已初始化
bool initialized_ = false;
/// 是否需要更新 UBO
bool ubo_dirty_ = true;
/// Vulkan 设备
std::shared_ptr<vulkan_device> device_;
/// GPU 分配器
std::shared_ptr<gpu_allocator> allocator_;
/// 描述符池
std::shared_ptr<descriptor_pool> pool_;
/// 颜色格式
vk::Format color_format_ = vk::Format::eB8G8R8A8Srgb;
/// 管线布局
std::unique_ptr<pipeline_layout> pipeline_layout_;
/// 图形管线
std::unique_ptr<graphics_pipeline> pipeline_;
/// 描述符集布局
vk::DescriptorSetLayout descriptor_set_layout_;
/// 描述符集
vk::DescriptorSet descriptor_set_;
/// 顶点缓冲区
std::unique_ptr<buffer> vertex_buffer_;
/// Uniform Buffer (Widget Bounds)
std::unique_ptr<buffer> uniform_buffer_;
/// Widget 边界数据
widget_bounds_ubo bounds_;
/// Push Constant 数据
gradient_push_constants push_constants_;
};
} // namespace demo
} // namespace mirai

View File

@@ -0,0 +1,559 @@
/**
* @file particle_widget.cpp
* @brief MIRAI 框架粒子系统 Widget 实现
*/
#include "particle_widget.hpp"
#include "vulkan_device.hpp"
#include "allocator.hpp"
#include "descriptor_pool.hpp"
#include "command_buffer.hpp"
#include "buffer.hpp"
#include "pipeline.hpp"
#include "logger.hpp"
#include <cmath>
#include <algorithm>
#include <chrono>
namespace mirai {
namespace demo {
// ================================================================================================
// particle_widget 实现
// ================================================================================================
particle_widget::particle_widget()
: rng_(static_cast<u32>(std::chrono::steady_clock::now().time_since_epoch().count())) {
// 初始化单位矩阵
std::fill(std::begin(push_constants_.view_proj), std::end(push_constants_.view_proj), 0.0f);
push_constants_.view_proj[0] = 1.0f;
push_constants_.view_proj[5] = 1.0f;
push_constants_.view_proj[10] = 1.0f;
push_constants_.view_proj[15] = 1.0f;
push_constants_.viewport_size[0] = 1280.0f;
push_constants_.viewport_size[1] = 720.0f;
}
particle_widget::~particle_widget() {
destroy();
}
bool particle_widget::initialize(
std::shared_ptr<vulkan_device> device,
std::shared_ptr<gpu_allocator> allocator,
std::shared_ptr<descriptor_pool> pool,
u32 max_particles,
vk::Format color_format) {
if (initialized_) {
MIRAI_LOG_WARN("particle_widget already initialized");
return true;
}
device_ = std::move(device);
allocator_ = std::move(allocator);
pool_ = std::move(pool);
max_particles_ = max_particles;
color_format_ = color_format;
// 预分配粒子和实例数据
particles_.reserve(max_particles_);
instances_.reserve(max_particles_);
// 创建资源
if (!create_buffers()) {
MIRAI_LOG_ERROR("Failed to create buffers for particle_widget");
return false;
}
if (!create_descriptor_sets()) {
MIRAI_LOG_ERROR("Failed to create descriptor sets for particle_widget");
return false;
}
if (!create_shader_modules()) {
MIRAI_LOG_ERROR("Failed to create shader modules for particle_widget");
return false;
}
if (!create_pipeline()) {
MIRAI_LOG_ERROR("Failed to create pipeline for particle_widget");
return false;
}
initialized_ = true;
MIRAI_LOG_INFO("particle_widget initialized successfully (max_particles={})", max_particles_);
return true;
}
void particle_widget::destroy() {
if (!initialized_) {
return;
}
// 等待设备空闲
if (device_) {
device_->get_device().waitIdle();
}
// 销毁着色器模块
if (vertex_shader_ && device_) {
device_->get_device().destroyShaderModule(vertex_shader_);
vertex_shader_ = nullptr;
}
if (fragment_shader_ && device_) {
device_->get_device().destroyShaderModule(fragment_shader_);
fragment_shader_ = nullptr;
}
// 销毁描述符集布局
if (descriptor_set_layout_ && device_) {
device_->get_device().destroyDescriptorSetLayout(descriptor_set_layout_);
descriptor_set_layout_ = nullptr;
}
// 销毁管线
pipeline_.reset();
pipeline_layout_.reset();
// 销毁缓冲区
instance_buffer_.reset();
uniform_buffer_.reset();
// 清理粒子数据
particles_.clear();
instances_.clear();
// 清理引用
device_.reset();
allocator_.reset();
pool_.reset();
initialized_ = false;
MIRAI_LOG_INFO("particle_widget destroyed");
}
void particle_widget::set_bounds(f32 x, f32 y, f32 width, f32 height) {
bounds_.bounds[0] = x;
bounds_.bounds[1] = y;
bounds_.bounds[2] = width;
bounds_.bounds[3] = height;
ubo_dirty_ = true;
}
void particle_widget::set_opacity(f32 opacity) {
bounds_.opacity = std::clamp(opacity, 0.0f, 1.0f);
ubo_dirty_ = true;
}
void particle_widget::set_viewport_size(u32 width, u32 height) {
push_constants_.viewport_size[0] = static_cast<f32>(width);
push_constants_.viewport_size[1] = static_cast<f32>(height);
}
void particle_widget::set_emitter_config(const particle_emitter_config& config) {
emitter_config_ = config;
}
void particle_widget::emit(f32 delta_time) {
emit_accumulator_ += emitter_config_.emit_rate * delta_time;
while (emit_accumulator_ >= 1.0f && particles_.size() < max_particles_) {
emit_accumulator_ -= 1.0f;
emit_burst(1);
}
}
void particle_widget::emit_burst(u32 count) {
for (u32 i = 0; i < count && particles_.size() < max_particles_; ++i) {
particle p;
// 位置
p.position[0] = emitter_config_.emit_position[0] +
random_float(-1.0f, 1.0f) * emitter_config_.emit_position_variance[0];
p.position[1] = emitter_config_.emit_position[1] +
random_float(-1.0f, 1.0f) * emitter_config_.emit_position_variance[1];
p.position[2] = emitter_config_.emit_position[2] +
random_float(-1.0f, 1.0f) * emitter_config_.emit_position_variance[2];
// 速度
p.velocity[0] = emitter_config_.initial_velocity[0] +
random_float(-1.0f, 1.0f) * emitter_config_.velocity_variance[0];
p.velocity[1] = emitter_config_.initial_velocity[1] +
random_float(-1.0f, 1.0f) * emitter_config_.velocity_variance[1];
p.velocity[2] = emitter_config_.initial_velocity[2] +
random_float(-1.0f, 1.0f) * emitter_config_.velocity_variance[2];
// 颜色(初始为起始颜色)
p.color = emitter_config_.start_color;
// 大小
p.size = emitter_config_.start_size +
random_float(-1.0f, 1.0f) * emitter_config_.size_variance;
// 生命周期
p.life = emitter_config_.life +
random_float(-1.0f, 1.0f) * emitter_config_.life_variance;
p.max_life = p.life;
// 旋转
p.rotation = random_float(0.0f, 6.28318f);
p.rotation_speed = emitter_config_.rotation_speed +
random_float(-1.0f, 1.0f) * emitter_config_.rotation_speed_variance;
particles_.push_back(p);
}
instances_dirty_ = true;
}
void particle_widget::simulate(f32 delta_time) {
if (particles_.empty()) {
return;
}
// 更新所有粒子
for (auto& p : particles_) {
// 更新生命周期
p.life -= delta_time;
// 应用重力
p.velocity[0] += emitter_config_.gravity[0] * delta_time;
p.velocity[1] += emitter_config_.gravity[1] * delta_time;
p.velocity[2] += emitter_config_.gravity[2] * delta_time;
// 更新位置
p.position[0] += p.velocity[0] * delta_time;
p.position[1] += p.velocity[1] * delta_time;
p.position[2] += p.velocity[2] * delta_time;
// 更新旋转
p.rotation += p.rotation_speed * delta_time;
// 计算生命周期比例
f32 life_ratio = 1.0f - (p.life / p.max_life);
life_ratio = std::clamp(life_ratio, 0.0f, 1.0f);
// 插值颜色
for (int j = 0; j < 4; ++j) {
p.color[j] = emitter_config_.start_color[j] * (1.0f - life_ratio) +
emitter_config_.end_color[j] * life_ratio;
}
// 插值大小
p.size = emitter_config_.start_size * (1.0f - life_ratio) +
emitter_config_.end_size * life_ratio;
}
// 移除死亡粒子
particles_.erase(
std::remove_if(particles_.begin(), particles_.end(),
[](const particle& p) { return p.life <= 0.0f; }),
particles_.end()
);
instances_dirty_ = true;
}
void particle_widget::clear() {
particles_.clear();
instances_.clear();
instances_dirty_ = true;
}
void particle_widget::update(command_buffer& /*cmd*/) {
if (!initialized_) {
return;
}
// 更新 UBO
if (ubo_dirty_ && uniform_buffer_) {
uniform_buffer_->write(&bounds_, sizeof(bounds_), 0);
ubo_dirty_ = false;
}
// 更新实例缓冲区
if (instances_dirty_) {
update_instance_buffer();
instances_dirty_ = false;
}
}
void particle_widget::render(command_buffer& cmd) {
if (!initialized_ || !pipeline_ || !pipeline_->is_valid()) {
return;
}
if (particles_.empty()) {
return;
}
auto vk_cmd = cmd.get_vulkan_buffer();
auto vk_pipeline = pipeline_->get_vulkan_pipeline();
auto vk_layout = pipeline_->get_layout();
// 绑定管线
vk_cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, vk_pipeline);
// 绑定描述符集
vk_cmd.bindDescriptorSets(
vk::PipelineBindPoint::eGraphics,
vk_layout,
0,
1,
&descriptor_set_,
0,
nullptr
);
// 推送常量
vk_cmd.pushConstants(
vk_layout,
vk::ShaderStageFlagBits::eVertex,
0,
sizeof(push_constants_),
&push_constants_
);
// 绑定实例缓冲区
vk::Buffer buffers[] = {instance_buffer_->get_vulkan_buffer()};
vk::DeviceSize offsets[] = {0};
vk_cmd.bindVertexBuffers(0, 1, buffers, offsets);
// 绘制每个粒子6个顶点实例化渲染
u32 particle_count = static_cast<u32>(particles_.size());
vk_cmd.draw(6, particle_count, 0, 0);
}
bool particle_widget::create_pipeline() {
if (!vertex_shader_ || !fragment_shader_) {
MIRAI_LOG_ERROR("Shader modules not created");
return false;
}
// 创建管线布局
vk::PushConstantRange push_constant_range{
vk::ShaderStageFlagBits::eVertex,
0,
sizeof(particle_push_constants)
};
pipeline_layout_config layout_config;
layout_config.descriptor_set_layouts = {descriptor_set_layout_};
layout_config.push_constant_ranges = {push_constant_range};
pipeline_layout_ = std::make_unique<pipeline_layout>(device_, layout_config);
if (!pipeline_layout_->is_valid()) {
MIRAI_LOG_ERROR("Failed to create pipeline layout");
return false;
}
// 使用构建器创建管线
graphics_pipeline_builder builder(device_);
pipeline_ = builder
.set_vertex_shader(vertex_shader_, "main")
.set_fragment_shader(fragment_shader_, "main")
// 实例数据输入
.add_vertex_binding(0, sizeof(particle_instance), vk::VertexInputRate::eInstance)
.add_vertex_attribute(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(particle_instance, position))
.add_vertex_attribute(1, 0, vk::Format::eR32Sfloat, offsetof(particle_instance, size))
.add_vertex_attribute(2, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(particle_instance, color))
.add_vertex_attribute(3, 0, vk::Format::eR32Sfloat, offsetof(particle_instance, rotation))
.set_topology(vk::PrimitiveTopology::eTriangleList)
.set_polygon_mode(vk::PolygonMode::eFill)
.set_cull_mode(vk::CullModeFlagBits::eNone)
.set_front_face(vk::FrontFace::eCounterClockwise)
.enable_depth_test(false)
.enable_depth_write(false)
.enable_blending(true)
.set_blend_factors(
vk::BlendFactor::eSrcAlpha,
vk::BlendFactor::eOne, // 加法混合,适合发光粒子
vk::BlendFactor::eOne,
vk::BlendFactor::eOne)
.set_color_format(color_format_)
.add_dynamic_state(vk::DynamicState::eViewport)
.add_dynamic_state(vk::DynamicState::eScissor)
.set_layout(*pipeline_layout_)
.build();
if (!pipeline_ || !pipeline_->is_valid()) {
MIRAI_LOG_ERROR("Failed to create graphics pipeline");
return false;
}
return true;
}
bool particle_widget::create_descriptor_sets() {
auto vk_device = device_->get_device();
// 创建描述符集布局
vk::DescriptorSetLayoutBinding binding{
0,
vk::DescriptorType::eUniformBuffer,
1,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
nullptr
};
vk::DescriptorSetLayoutCreateInfo layout_info{{}, 1, &binding};
auto [layout_result, layout] = vk_device.createDescriptorSetLayout(layout_info);
if (layout_result != vk::Result::eSuccess) {
MIRAI_LOG_ERROR("Failed to create descriptor set layout");
return false;
}
descriptor_set_layout_ = layout;
// 分配描述符集
auto set_result = pool_->allocate(descriptor_set_layout_);
if (!set_result) {
MIRAI_LOG_ERROR("Failed to allocate descriptor set");
return false;
}
descriptor_set_ = set_result.value()->get_vulkan_set();
// 更新描述符集
vk::DescriptorBufferInfo buffer_info{
uniform_buffer_->get_vulkan_buffer(),
0,
sizeof(particle_bounds_ubo)
};
vk::WriteDescriptorSet write{
descriptor_set_,
0,
0,
1,
vk::DescriptorType::eUniformBuffer,
nullptr,
&buffer_info,
nullptr
};
vk_device.updateDescriptorSets(1, &write, 0, nullptr);
return true;
}
bool particle_widget::create_buffers() {
// 创建 Uniform Buffer
{
buffer_create_info info;
info.size = sizeof(particle_bounds_ubo);
info.usage = buffer_usage::uniform;
info.mem_usage = memory_usage::cpu_to_gpu;
info.persistent_mapped = true;
info.debug_name = "particle_widget_uniform_buffer";
uniform_buffer_ = std::make_unique<buffer>(allocator_, info);
if (!uniform_buffer_->is_valid()) {
MIRAI_LOG_ERROR("Failed to create uniform buffer");
return false;
}
uniform_buffer_->write(&bounds_, sizeof(bounds_), 0);
}
// 创建实例缓冲区
{
buffer_create_info info;
info.size = sizeof(particle_instance) * max_particles_;
info.usage = buffer_usage::vertex;
info.mem_usage = memory_usage::cpu_to_gpu;
info.persistent_mapped = true;
info.debug_name = "particle_widget_instance_buffer";
instance_buffer_ = std::make_unique<buffer>(allocator_, info);
if (!instance_buffer_->is_valid()) {
MIRAI_LOG_ERROR("Failed to create instance buffer");
return false;
}
}
return true;
}
bool particle_widget::create_shader_modules() {
// 粒子系统使用 fullscreen 顶点着色器作为基础
// 实际项目中应该使用专门的粒子着色器
// 这里我们使用 fullscreen 着色器作为临时替代
auto vk_device = device_->get_device();
// 使用 fullscreen 顶点着色器
auto vert_spirv = shaders::fullscreen::vertex::spirv();
// 使用 gradient 片段着色器作为临时替代
// 因为它也输出颜色
auto frag_spirv = shaders::gradient::fragment::spirv();
if (vert_spirv.empty() || frag_spirv.empty()) {
MIRAI_LOG_ERROR("Failed to get shader SPIR-V data");
return false;
}
// 创建顶点着色器模块
vk::ShaderModuleCreateInfo vert_info{{}, vert_spirv.size() * sizeof(u32), vert_spirv.data()};
auto [vert_result, vert_module] = vk_device.createShaderModule(vert_info);
if (vert_result != vk::Result::eSuccess) {
MIRAI_LOG_ERROR("Failed to create vertex shader module");
return false;
}
vertex_shader_ = vert_module;
// 创建片段着色器模块
vk::ShaderModuleCreateInfo frag_info{{}, frag_spirv.size() * sizeof(u32), frag_spirv.data()};
auto [frag_result, frag_module] = vk_device.createShaderModule(frag_info);
if (frag_result != vk::Result::eSuccess) {
vk_device.destroyShaderModule(vertex_shader_);
vertex_shader_ = nullptr;
MIRAI_LOG_ERROR("Failed to create fragment shader module");
return false;
}
fragment_shader_ = frag_module;
MIRAI_LOG_WARN("particle_widget: Using placeholder shaders. For proper particle rendering, "
"add particle.vert and particle.frag to the shader compilation system.");
return true;
}
f32 particle_widget::random_float(f32 min, f32 max) {
return min + dist_(rng_) * (max - min);
}
void particle_widget::update_instance_buffer() {
if (!instance_buffer_ || particles_.empty()) {
return;
}
// 更新实例数据
instances_.resize(particles_.size());
for (size_t i = 0; i < particles_.size(); ++i) {
const auto& p = particles_[i];
auto& inst = instances_[i];
inst.position[0] = p.position[0];
inst.position[1] = p.position[1];
inst.position[2] = p.position[2];
inst.size = p.size;
inst.color[0] = p.color[0];
inst.color[1] = p.color[1];
inst.color[2] = p.color[2];
inst.color[3] = p.color[3];
inst.rotation = p.rotation;
}
// 写入缓冲区
instance_buffer_->write(instances_.data(),
instances_.size() * sizeof(particle_instance), 0);
}
} // namespace demo
} // namespace mirai

View File

@@ -0,0 +1,469 @@
/**
* @file particle_widget.hpp
* @brief MIRAI 框架粒子系统 Widget
* @author MIRAI Team
* @version 0.1.0
*
* 使用 Custom Mesh 模式实现粒子系统。
* 使用自定义顶点着色器和片段着色器,支持实例化渲染。
*/
#pragma once
#include "types/types.hpp"
#include "vulkan_types.hpp"
#include "object.hpp"
#include <memory>
#include <array>
#include <vector>
#include <random>
namespace mirai {
// 前向声明
class vulkan_device;
class gpu_allocator;
class descriptor_pool;
class command_buffer;
class buffer;
class graphics_pipeline;
class pipeline_layout;
namespace demo {
// ================================================================================================
// 粒子数据结构
// ================================================================================================
/**
* @brief 单个粒子数据
*/
struct particle {
/// 位置 (x, y, z)
std::array<f32, 3> position = {0.0f, 0.0f, 0.0f};
/// 速度 (vx, vy, vz)
std::array<f32, 3> velocity = {0.0f, 0.0f, 0.0f};
/// 颜色 (RGBA)
std::array<f32, 4> color = {1.0f, 1.0f, 1.0f, 1.0f};
/// 大小
f32 size = 10.0f;
/// 生命周期(秒)
f32 life = 1.0f;
/// 最大生命周期
f32 max_life = 1.0f;
/// 旋转角度(弧度)
f32 rotation = 0.0f;
/// 旋转速度
f32 rotation_speed = 0.0f;
};
/**
* @brief 粒子实例数据GPU 端)
*
* 用于实例化渲染的顶点属性
*/
struct alignas(16) particle_instance {
/// 位置 (x, y, z)
f32 position[3];
/// 大小
f32 size;
/// 颜色 (RGBA)
f32 color[4];
/// 旋转角度
f32 rotation;
/// 填充
f32 _pad[3];
};
static_assert(sizeof(particle_instance) == 48, "particle_instance size mismatch");
// ================================================================================================
// Push Constant 结构
// ================================================================================================
/**
* @brief 粒子系统 Push Constant 结构
*/
struct alignas(16) particle_push_constants {
/// 视图投影矩阵
f32 view_proj[16];
/// 视口大小 (width, height)
f32 viewport_size[2];
/// 填充
f32 _pad[2];
};
static_assert(sizeof(particle_push_constants) == 80, "particle_push_constants size mismatch");
// ================================================================================================
// Widget Bounds UBO 结构
// ================================================================================================
/**
* @brief Widget 边界 UBO 结构
*/
struct alignas(16) particle_bounds_ubo {
/// 边界 (x, y, width, height)
std::array<f32, 4> bounds = {0.0f, 0.0f, 1.0f, 1.0f};
/// 透明度
f32 opacity = 1.0f;
/// 填充
f32 _pad[3] = {0.0f, 0.0f, 0.0f};
};
static_assert(sizeof(particle_bounds_ubo) == 32, "particle_bounds_ubo size mismatch");
// ================================================================================================
// 粒子发射器配置
// ================================================================================================
/**
* @brief 粒子发射器配置
*/
struct particle_emitter_config {
/// 发射位置
std::array<f32, 3> emit_position = {0.5f, 0.5f, 0.0f};
/// 发射位置随机范围
std::array<f32, 3> emit_position_variance = {0.1f, 0.1f, 0.0f};
/// 初始速度
std::array<f32, 3> initial_velocity = {0.0f, -0.2f, 0.0f};
/// 速度随机范围
std::array<f32, 3> velocity_variance = {0.1f, 0.1f, 0.0f};
/// 重力
std::array<f32, 3> gravity = {0.0f, 0.5f, 0.0f};
/// 初始颜色
std::array<f32, 4> start_color = {1.0f, 0.5f, 0.0f, 1.0f};
/// 结束颜色
std::array<f32, 4> end_color = {1.0f, 0.0f, 0.0f, 0.0f};
/// 初始大小
f32 start_size = 20.0f;
/// 结束大小
f32 end_size = 5.0f;
/// 大小随机范围
f32 size_variance = 5.0f;
/// 生命周期
f32 life = 2.0f;
/// 生命周期随机范围
f32 life_variance = 0.5f;
/// 旋转速度
f32 rotation_speed = 0.0f;
/// 旋转速度随机范围
f32 rotation_speed_variance = 1.0f;
/// 每秒发射数量
f32 emit_rate = 50.0f;
};
// ================================================================================================
// particle_widget 类
// ================================================================================================
/**
* @brief 粒子系统 Widget
*
* 使用 Custom Mesh 模式实现粒子系统,支持实例化渲染。
*
* @example
* @code
* auto widget = std::make_unique<particle_widget>();
* widget->initialize(device, allocator, pool, 1000);
* widget->set_bounds(0.0f, 0.0f, 1.0f, 1.0f);
*
* particle_emitter_config config;
* config.emit_position = {0.5f, 0.8f, 0.0f};
* config.start_color = {1.0f, 0.5f, 0.0f, 1.0f};
* widget->set_emitter_config(config);
*
* // 在更新循环中
* widget->emit(delta_time);
* widget->simulate(delta_time);
* widget->update(cmd);
*
* // 在渲染循环中
* widget->render(cmd);
* @endcode
*/
class particle_widget : public object {
public:
MIRAI_OBJECT_TYPE_INFO(particle_widget, object)
/**
* @brief 默认构造函数
*/
particle_widget();
/**
* @brief 析构函数
*/
~particle_widget() override;
// 禁止拷贝和移动
particle_widget(const particle_widget&) = delete;
particle_widget& operator=(const particle_widget&) = delete;
particle_widget(particle_widget&&) = delete;
particle_widget& operator=(particle_widget&&) = delete;
// ============================================================================================
// 生命周期
// ============================================================================================
/**
* @brief 初始化 Widget
* @param device Vulkan 设备
* @param allocator GPU 内存分配器
* @param pool 描述符池
* @param max_particles 最大粒子数量
* @param color_format 颜色附件格式
* @return 是否成功
*/
[[nodiscard]] bool initialize(
std::shared_ptr<vulkan_device> device,
std::shared_ptr<gpu_allocator> allocator,
std::shared_ptr<descriptor_pool> pool,
u32 max_particles = 1000,
vk::Format color_format = vk::Format::eB8G8R8A8Srgb);
/**
* @brief 销毁 Widget 资源
*/
void destroy();
/**
* @brief 检查是否已初始化
* @return 是否已初始化
*/
[[nodiscard]] bool is_initialized() const noexcept { return initialized_; }
// ============================================================================================
// 参数设置
// ============================================================================================
/**
* @brief 设置 Widget 边界
* @param x X 坐标(归一化 0-1
* @param y Y 坐标(归一化 0-1
* @param width 宽度(归一化 0-1
* @param height 高度(归一化 0-1
*/
void set_bounds(f32 x, f32 y, f32 width, f32 height);
/**
* @brief 设置透明度
* @param opacity 透明度 (0-1)
*/
void set_opacity(f32 opacity);
/**
* @brief 设置视口大小
* @param width 视口宽度
* @param height 视口高度
*/
void set_viewport_size(u32 width, u32 height);
/**
* @brief 设置发射器配置
* @param config 发射器配置
*/
void set_emitter_config(const particle_emitter_config& config);
/**
* @brief 获取发射器配置
* @return 发射器配置引用
*/
[[nodiscard]] particle_emitter_config& emitter_config() noexcept { return emitter_config_; }
[[nodiscard]] const particle_emitter_config& emitter_config() const noexcept { return emitter_config_; }
// ============================================================================================
// 粒子操作
// ============================================================================================
/**
* @brief 发射粒子
* @param delta_time 时间增量(秒)
*/
void emit(f32 delta_time);
/**
* @brief 发射指定数量的粒子
* @param count 粒子数量
*/
void emit_burst(u32 count);
/**
* @brief 模拟粒子运动
* @param delta_time 时间增量(秒)
*/
void simulate(f32 delta_time);
/**
* @brief 清除所有粒子
*/
void clear();
/**
* @brief 获取活跃粒子数量
* @return 活跃粒子数量
*/
[[nodiscard]] u32 active_particle_count() const noexcept {
return static_cast<u32>(particles_.size());
}
/**
* @brief 获取最大粒子数量
* @return 最大粒子数量
*/
[[nodiscard]] u32 max_particle_count() const noexcept { return max_particles_; }
// ============================================================================================
// 渲染
// ============================================================================================
/**
* @brief 更新 GPU 缓冲区
* @param cmd 命令缓冲区
*/
void update(command_buffer& cmd);
/**
* @brief 渲染粒子
* @param cmd 命令缓冲区
*/
void render(command_buffer& cmd);
// ============================================================================================
// 属性访问
// ============================================================================================
/**
* @brief 获取 X 坐标
*/
[[nodiscard]] f32 x() const noexcept { return bounds_.bounds[0]; }
/**
* @brief 获取 Y 坐标
*/
[[nodiscard]] f32 y() const noexcept { return bounds_.bounds[1]; }
/**
* @brief 获取宽度
*/
[[nodiscard]] f32 width() const noexcept { return bounds_.bounds[2]; }
/**
* @brief 获取高度
*/
[[nodiscard]] f32 height() const noexcept { return bounds_.bounds[3]; }
/**
* @brief 获取透明度
*/
[[nodiscard]] f32 opacity() const noexcept { return bounds_.opacity; }
private:
/**
* @brief 创建管线
* @return 是否成功
*/
[[nodiscard]] bool create_pipeline();
/**
* @brief 创建描述符集
* @return 是否成功
*/
[[nodiscard]] bool create_descriptor_sets();
/**
* @brief 创建缓冲区
* @return 是否成功
*/
[[nodiscard]] bool create_buffers();
/**
* @brief 创建着色器模块
* @return 是否成功
*/
[[nodiscard]] bool create_shader_modules();
/**
* @brief 生成随机浮点数
* @param min 最小值
* @param max 最大值
* @return 随机值
*/
[[nodiscard]] f32 random_float(f32 min, f32 max);
/**
* @brief 更新实例缓冲区
*/
void update_instance_buffer();
/// 是否已初始化
bool initialized_ = false;
/// 是否需要更新 UBO
bool ubo_dirty_ = true;
/// 是否需要更新实例缓冲区
bool instances_dirty_ = true;
/// 最大粒子数量
u32 max_particles_ = 1000;
/// Vulkan 设备
std::shared_ptr<vulkan_device> device_;
/// GPU 分配器
std::shared_ptr<gpu_allocator> allocator_;
/// 描述符池
std::shared_ptr<descriptor_pool> pool_;
/// 颜色格式
vk::Format color_format_ = vk::Format::eB8G8R8A8Srgb;
/// 管线布局
std::unique_ptr<pipeline_layout> pipeline_layout_;
/// 图形管线
std::unique_ptr<graphics_pipeline> pipeline_;
/// 描述符集布局
vk::DescriptorSetLayout descriptor_set_layout_;
/// 描述符集
vk::DescriptorSet descriptor_set_;
/// 顶点着色器模块
vk::ShaderModule vertex_shader_;
/// 片段着色器模块
vk::ShaderModule fragment_shader_;
/// 实例缓冲区
std::unique_ptr<buffer> instance_buffer_;
/// Uniform Buffer (Widget Bounds)
std::unique_ptr<buffer> uniform_buffer_;
/// Widget 边界数据
particle_bounds_ubo bounds_;
/// Push Constant 数据
particle_push_constants push_constants_;
/// 发射器配置
particle_emitter_config emitter_config_;
/// 粒子列表
std::vector<particle> particles_;
/// 实例数据(用于上传到 GPU
std::vector<particle_instance> instances_;
/// 发射累积器
f32 emit_accumulator_ = 0.0f;
/// 随机数生成器
std::mt19937 rng_;
std::uniform_real_distribution<f32> dist_{0.0f, 1.0f};
};
} // namespace demo
} // namespace mirai

View File

@@ -0,0 +1,350 @@
/**
* @file rounded_rect_widget.cpp
* @brief MIRAI 框架圆角矩形 Widget 实现
*/
#include "rounded_rect_widget.hpp"
#include "vulkan_device.hpp"
#include "allocator.hpp"
#include "descriptor_pool.hpp"
#include "command_buffer.hpp"
#include "buffer.hpp"
#include "pipeline.hpp"
#include "logger.hpp"
#include <cmath>
#include <algorithm>
namespace mirai {
namespace demo {
// ================================================================================================
// rounded_rect_widget 实现
// ================================================================================================
rounded_rect_widget::rounded_rect_widget() {
// 默认颜色:白色
push_constants_.color[0] = 1.0f;
push_constants_.color[1] = 1.0f;
push_constants_.color[2] = 1.0f;
push_constants_.color[3] = 1.0f;
// 默认圆角半径
push_constants_.radius = 16.0f;
}
rounded_rect_widget::~rounded_rect_widget() {
destroy();
}
bool rounded_rect_widget::initialize(
std::shared_ptr<vulkan_device> device,
std::shared_ptr<gpu_allocator> allocator,
std::shared_ptr<descriptor_pool> pool,
vk::Format color_format) {
if (initialized_) {
MIRAI_LOG_WARN("rounded_rect_widget already initialized");
return true;
}
device_ = std::move(device);
allocator_ = std::move(allocator);
pool_ = std::move(pool);
color_format_ = color_format;
// 创建资源
if (!create_uniform_buffer()) {
MIRAI_LOG_ERROR("Failed to create uniform buffer for rounded_rect_widget");
return false;
}
if (!create_descriptor_sets()) {
MIRAI_LOG_ERROR("Failed to create descriptor sets for rounded_rect_widget");
return false;
}
if (!create_pipeline()) {
MIRAI_LOG_ERROR("Failed to create pipeline for rounded_rect_widget");
return false;
}
initialized_ = true;
MIRAI_LOG_INFO("rounded_rect_widget initialized successfully");
return true;
}
void rounded_rect_widget::destroy() {
if (!initialized_) {
return;
}
// 等待设备空闲
if (device_) {
device_->get_device().waitIdle();
}
// 销毁描述符集布局
if (descriptor_set_layout_ && device_) {
device_->get_device().destroyDescriptorSetLayout(descriptor_set_layout_);
descriptor_set_layout_ = nullptr;
}
// 销毁管线
pipeline_.reset();
pipeline_layout_.reset();
// 销毁缓冲区
uniform_buffer_.reset();
vertex_buffer_.reset();
// 清理引用
device_.reset();
allocator_.reset();
pool_.reset();
initialized_ = false;
MIRAI_LOG_INFO("rounded_rect_widget destroyed");
}
void rounded_rect_widget::set_bounds(f32 x, f32 y, f32 width, f32 height) {
bounds_.bounds[0] = x;
bounds_.bounds[1] = y;
bounds_.bounds[2] = width;
bounds_.bounds[3] = height;
ubo_dirty_ = true;
}
void rounded_rect_widget::set_bounds_pixels(f32 x, f32 y, f32 width, f32 height,
u32 screen_width, u32 screen_height) {
bounds_.bounds[0] = x / static_cast<f32>(screen_width);
bounds_.bounds[1] = y / static_cast<f32>(screen_height);
bounds_.bounds[2] = width / static_cast<f32>(screen_width);
bounds_.bounds[3] = height / static_cast<f32>(screen_height);
ubo_dirty_ = true;
}
void rounded_rect_widget::set_opacity(f32 opacity) {
bounds_.opacity = std::clamp(opacity, 0.0f, 1.0f);
ubo_dirty_ = true;
}
void rounded_rect_widget::set_color(const std::array<f32, 4>& color) {
push_constants_.color = color;
}
void rounded_rect_widget::set_radius(f32 radius) {
push_constants_.radius = std::max(0.0f, radius);
}
void rounded_rect_widget::update(command_buffer& /*cmd*/) {
if (!initialized_) {
return;
}
// 更新 UBO
if (ubo_dirty_ && uniform_buffer_) {
uniform_buffer_->write(&bounds_, sizeof(bounds_), 0);
ubo_dirty_ = false;
}
}
void rounded_rect_widget::render(command_buffer& cmd) {
if (!initialized_ || !pipeline_ || !pipeline_->is_valid()) {
return;
}
auto vk_cmd = cmd.get_vulkan_buffer();
auto vk_pipeline = pipeline_->get_vulkan_pipeline();
auto vk_layout = pipeline_->get_layout();
// 绑定管线
vk_cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, vk_pipeline);
// 绑定描述符集
vk_cmd.bindDescriptorSets(
vk::PipelineBindPoint::eGraphics,
vk_layout,
0,
1,
&descriptor_set_,
0,
nullptr
);
// 推送常量
vk_cmd.pushConstants(
vk_layout,
vk::ShaderStageFlagBits::eFragment,
0,
sizeof(push_constants_),
&push_constants_
);
// 绘制全屏四边形
vk_cmd.draw(6, 1, 0, 0);
}
bool rounded_rect_widget::create_pipeline() {
auto vk_device = device_->get_device();
// 获取着色器 SPIR-V
auto vert_spirv = shaders::quad::vertex::spirv();
auto frag_spirv = shaders::rounded_rect::fragment::spirv();
if (vert_spirv.empty() || frag_spirv.empty()) {
MIRAI_LOG_ERROR("Failed to get shader SPIR-V data");
return false;
}
// 创建着色器模块
vk::ShaderModuleCreateInfo vert_info{{}, vert_spirv.size() * sizeof(u32), vert_spirv.data()};
auto [vert_result, vert_module] = vk_device.createShaderModule(vert_info);
if (vert_result != vk::Result::eSuccess) {
MIRAI_LOG_ERROR("Failed to create vertex shader module");
return false;
}
vk::ShaderModuleCreateInfo frag_info{{}, frag_spirv.size() * sizeof(u32), frag_spirv.data()};
auto [frag_result, frag_module] = vk_device.createShaderModule(frag_info);
if (frag_result != vk::Result::eSuccess) {
vk_device.destroyShaderModule(vert_module);
MIRAI_LOG_ERROR("Failed to create fragment shader module");
return false;
}
// 创建管线布局
vk::PushConstantRange push_constant_range{
vk::ShaderStageFlagBits::eFragment,
0,
sizeof(rounded_rect_push_constants)
};
pipeline_layout_config layout_config;
layout_config.descriptor_set_layouts = {descriptor_set_layout_};
layout_config.push_constant_ranges = {push_constant_range};
pipeline_layout_ = std::make_unique<pipeline_layout>(device_, layout_config);
if (!pipeline_layout_->is_valid()) {
vk_device.destroyShaderModule(vert_module);
vk_device.destroyShaderModule(frag_module);
MIRAI_LOG_ERROR("Failed to create pipeline layout");
return false;
}
// 使用构建器创建管线
graphics_pipeline_builder builder(device_);
pipeline_ = builder
.set_vertex_shader(vert_module, "main")
.set_fragment_shader(frag_module, "main")
.set_topology(vk::PrimitiveTopology::eTriangleList)
.set_polygon_mode(vk::PolygonMode::eFill)
.set_cull_mode(vk::CullModeFlagBits::eNone)
.set_front_face(vk::FrontFace::eCounterClockwise)
.enable_depth_test(false)
.enable_depth_write(false)
.enable_blending(true)
.set_blend_factors(
vk::BlendFactor::eSrcAlpha,
vk::BlendFactor::eOneMinusSrcAlpha,
vk::BlendFactor::eOne,
vk::BlendFactor::eOneMinusSrcAlpha)
.set_color_format(color_format_)
.add_dynamic_state(vk::DynamicState::eViewport)
.add_dynamic_state(vk::DynamicState::eScissor)
.set_layout(*pipeline_layout_)
.build();
// 销毁着色器模块(管线创建后不再需要)
vk_device.destroyShaderModule(vert_module);
vk_device.destroyShaderModule(frag_module);
if (!pipeline_ || !pipeline_->is_valid()) {
MIRAI_LOG_ERROR("Failed to create graphics pipeline");
return false;
}
return true;
}
bool rounded_rect_widget::create_descriptor_sets() {
auto vk_device = device_->get_device();
// 创建描述符集布局
vk::DescriptorSetLayoutBinding binding{
0,
vk::DescriptorType::eUniformBuffer,
1,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
nullptr
};
vk::DescriptorSetLayoutCreateInfo layout_info{{}, 1, &binding};
auto [layout_result, layout] = vk_device.createDescriptorSetLayout(layout_info);
if (layout_result != vk::Result::eSuccess) {
MIRAI_LOG_ERROR("Failed to create descriptor set layout");
return false;
}
descriptor_set_layout_ = layout;
// 分配描述符集
auto set_result = pool_->allocate(descriptor_set_layout_);
if (!set_result) {
MIRAI_LOG_ERROR("Failed to allocate descriptor set");
return false;
}
descriptor_set_ = set_result.value()->get_vulkan_set();
// 更新描述符集
vk::DescriptorBufferInfo buffer_info{
uniform_buffer_->get_vulkan_buffer(),
0,
sizeof(rounded_rect_bounds_ubo)
};
vk::WriteDescriptorSet write{
descriptor_set_,
0,
0,
1,
vk::DescriptorType::eUniformBuffer,
nullptr,
&buffer_info,
nullptr
};
vk_device.updateDescriptorSets(1, &write, 0, nullptr);
return true;
}
bool rounded_rect_widget::create_vertex_buffer() {
// 不需要顶点缓冲区,使用 gl_VertexIndex 生成顶点
return true;
}
bool rounded_rect_widget::create_uniform_buffer() {
buffer_create_info info;
info.size = sizeof(rounded_rect_bounds_ubo);
info.usage = buffer_usage::uniform;
info.mem_usage = memory_usage::cpu_to_gpu;
info.persistent_mapped = true;
info.debug_name = "rounded_rect_widget_uniform_buffer";
uniform_buffer_ = std::make_unique<buffer>(allocator_, info);
if (!uniform_buffer_->is_valid()) {
MIRAI_LOG_ERROR("Failed to create uniform buffer");
return false;
}
// 初始化 UBO 数据
uniform_buffer_->write(&bounds_, sizeof(bounds_), 0);
return true;
}
} // namespace demo
} // namespace mirai

View File

@@ -0,0 +1,307 @@
/**
* @file rounded_rect_widget.hpp
* @brief MIRAI 框架圆角矩形 Widget
* @author MIRAI Team
* @version 0.1.0
*
* 使用 Mask 模式实现圆角矩形效果。
* 使用 quad.vert + rounded_rect.frag 着色器。
*/
#pragma once
#include "types/types.hpp"
#include "vulkan_types.hpp"
#include "object.hpp"
#include <memory>
#include <array>
namespace mirai {
// 前向声明
class vulkan_device;
class gpu_allocator;
class descriptor_pool;
class command_buffer;
class buffer;
class graphics_pipeline;
class pipeline_layout;
namespace demo {
// ================================================================================================
// Push Constant 结构
// ================================================================================================
/**
* @brief 圆角矩形 Push Constant 结构
*
* 与 rounded_rect.frag 中的 PushConstants 布局匹配
*/
struct alignas(16) rounded_rect_push_constants {
/// 遮罩颜色 (RGBA)
std::array<f32, 4> color = {1.0f, 1.0f, 1.0f, 1.0f};
/// 圆角半径(像素)
f32 radius = 10.0f;
/// 填充对齐
f32 _pad[3] = {0.0f, 0.0f, 0.0f};
};
static_assert(sizeof(rounded_rect_push_constants) == 32, "rounded_rect_push_constants size mismatch");
// ================================================================================================
// Widget Bounds UBO 结构
// ================================================================================================
/**
* @brief Widget 边界 UBO 结构
*/
struct alignas(16) rounded_rect_bounds_ubo {
/// 边界 (x, y, width, height) - 归一化屏幕坐标
std::array<f32, 4> bounds = {0.0f, 0.0f, 1.0f, 1.0f};
/// 透明度
f32 opacity = 1.0f;
/// 填充
f32 _pad[3] = {0.0f, 0.0f, 0.0f};
};
static_assert(sizeof(rounded_rect_bounds_ubo) == 32, "rounded_rect_bounds_ubo size mismatch");
// ================================================================================================
// rounded_rect_widget 类
// ================================================================================================
/**
* @brief 圆角矩形 Widget
*
* 使用 Mask 模式实现圆角矩形效果,使用 SDF 实现高质量抗锯齿。
*
* @example
* @code
* auto widget = std::make_unique<rounded_rect_widget>();
* widget->initialize(device, allocator, pool);
* widget->set_bounds(0.1f, 0.1f, 0.3f, 0.3f);
* widget->set_color({0.2f, 0.5f, 0.8f, 1.0f});
* widget->set_radius(20.0f);
*
* // 在渲染循环中
* widget->update(cmd);
* widget->render(cmd);
* @endcode
*/
class rounded_rect_widget : public object {
public:
MIRAI_OBJECT_TYPE_INFO(rounded_rect_widget, object)
/**
* @brief 默认构造函数
*/
rounded_rect_widget();
/**
* @brief 析构函数
*/
~rounded_rect_widget() override;
// 禁止拷贝和移动
rounded_rect_widget(const rounded_rect_widget&) = delete;
rounded_rect_widget& operator=(const rounded_rect_widget&) = delete;
rounded_rect_widget(rounded_rect_widget&&) = delete;
rounded_rect_widget& operator=(rounded_rect_widget&&) = delete;
// ============================================================================================
// 生命周期
// ============================================================================================
/**
* @brief 初始化 Widget
* @param device Vulkan 设备
* @param allocator GPU 内存分配器
* @param pool 描述符池
* @param color_format 颜色附件格式
* @return 是否成功
*/
[[nodiscard]] bool initialize(
std::shared_ptr<vulkan_device> device,
std::shared_ptr<gpu_allocator> allocator,
std::shared_ptr<descriptor_pool> pool,
vk::Format color_format = vk::Format::eB8G8R8A8Srgb);
/**
* @brief 销毁 Widget 资源
*/
void destroy();
/**
* @brief 检查是否已初始化
* @return 是否已初始化
*/
[[nodiscard]] bool is_initialized() const noexcept { return initialized_; }
// ============================================================================================
// 参数设置
// ============================================================================================
/**
* @brief 设置 Widget 边界
* @param x X 坐标(归一化 0-1
* @param y Y 坐标(归一化 0-1
* @param width 宽度(归一化 0-1
* @param height 高度(归一化 0-1
*/
void set_bounds(f32 x, f32 y, f32 width, f32 height);
/**
* @brief 设置 Widget 边界(像素坐标)
* @param x X 坐标(像素)
* @param y Y 坐标(像素)
* @param width 宽度(像素)
* @param height 高度(像素)
* @param screen_width 屏幕宽度
* @param screen_height 屏幕高度
*/
void set_bounds_pixels(f32 x, f32 y, f32 width, f32 height,
u32 screen_width, u32 screen_height);
/**
* @brief 设置透明度
* @param opacity 透明度 (0-1)
*/
void set_opacity(f32 opacity);
/**
* @brief 设置颜色
* @param color 颜色 (RGBA)
*/
void set_color(const std::array<f32, 4>& color);
/**
* @brief 设置圆角半径
* @param radius 圆角半径(像素)
*/
void set_radius(f32 radius);
// ============================================================================================
// 渲染
// ============================================================================================
/**
* @brief 更新 Uniform Buffer
* @param cmd 命令缓冲区
*/
void update(command_buffer& cmd);
/**
* @brief 渲染 Widget
* @param cmd 命令缓冲区
*/
void render(command_buffer& cmd);
// ============================================================================================
// 属性访问
// ============================================================================================
/**
* @brief 获取 X 坐标
*/
[[nodiscard]] f32 x() const noexcept { return bounds_.bounds[0]; }
/**
* @brief 获取 Y 坐标
*/
[[nodiscard]] f32 y() const noexcept { return bounds_.bounds[1]; }
/**
* @brief 获取宽度
*/
[[nodiscard]] f32 width() const noexcept { return bounds_.bounds[2]; }
/**
* @brief 获取高度
*/
[[nodiscard]] f32 height() const noexcept { return bounds_.bounds[3]; }
/**
* @brief 获取透明度
*/
[[nodiscard]] f32 opacity() const noexcept { return bounds_.opacity; }
/**
* @brief 获取颜色
*/
[[nodiscard]] const std::array<f32, 4>& color() const noexcept { return push_constants_.color; }
/**
* @brief 获取圆角半径
*/
[[nodiscard]] f32 radius() const noexcept { return push_constants_.radius; }
private:
/**
* @brief 创建管线
* @return 是否成功
*/
[[nodiscard]] bool create_pipeline();
/**
* @brief 创建描述符集
* @return 是否成功
*/
[[nodiscard]] bool create_descriptor_sets();
/**
* @brief 创建顶点缓冲区
* @return 是否成功
*/
[[nodiscard]] bool create_vertex_buffer();
/**
* @brief 创建 Uniform Buffer
* @return 是否成功
*/
[[nodiscard]] bool create_uniform_buffer();
/// 是否已初始化
bool initialized_ = false;
/// 是否需要更新 UBO
bool ubo_dirty_ = true;
/// Vulkan 设备
std::shared_ptr<vulkan_device> device_;
/// GPU 分配器
std::shared_ptr<gpu_allocator> allocator_;
/// 描述符池
std::shared_ptr<descriptor_pool> pool_;
/// 颜色格式
vk::Format color_format_ = vk::Format::eB8G8R8A8Srgb;
/// 管线布局
std::unique_ptr<pipeline_layout> pipeline_layout_;
/// 图形管线
std::unique_ptr<graphics_pipeline> pipeline_;
/// 描述符集布局
vk::DescriptorSetLayout descriptor_set_layout_;
/// 描述符集
vk::DescriptorSet descriptor_set_;
/// 顶点缓冲区
std::unique_ptr<buffer> vertex_buffer_;
/// Uniform Buffer (Widget Bounds)
std::unique_ptr<buffer> uniform_buffer_;
/// Widget 边界数据
rounded_rect_bounds_ubo bounds_;
/// Push Constant 数据
rounded_rect_push_constants push_constants_;
};
} // namespace demo
} // namespace mirai

View File

@@ -0,0 +1,202 @@
/**
* @file render_context.cpp
* @brief MIRAI 框架渲染上下文实现
*/
#include "render_context.hpp"
namespace mirai {
render_context::render_context(renderer* renderer, vk::CommandBuffer cmd_buffer, render_phase phase)
: renderer_(renderer)
, cmd_buffer_(cmd_buffer)
, phase_(phase) {
}
render_context::~render_context() = default;
batch_renderer* render_context::get_batch_renderer() const {
if (renderer_) {
return renderer_->get_batch_renderer();
}
return nullptr;
}
u32 render_context::get_frame_index() const {
if (renderer_) {
return renderer_->get_current_frame_index();
}
return 0;
}
u32 render_context::get_image_index() const {
if (renderer_) {
return renderer_->get_current_image_index();
}
return 0;
}
vk::Extent2D render_context::get_swapchain_extent() const {
if (renderer_) {
return renderer_->get_swapchain_extent();
}
return vk::Extent2D{0, 0};
}
vk::Format render_context::get_swapchain_format() const {
if (renderer_) {
return renderer_->get_swapchain_format();
}
return vk::Format::eUndefined;
}
vk::Format render_context::get_depth_format() const {
// 默认使用 D32Sfloat
return vk::Format::eD32Sfloat;
}
void render_context::bind_pipeline(vk::Pipeline pipeline) {
if (cmd_buffer_) {
cmd_buffer_.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
}
}
void render_context::bind_pipeline_layout(vk::PipelineLayout layout) {
current_pipeline_layout_ = layout;
}
void render_context::bind_descriptor_sets(vk::PipelineBindPoint bind_point,
vk::PipelineLayout layout,
u32 first_set,
std::span<const vk::DescriptorSet> descriptor_sets,
std::span<const u32> dynamic_offsets) {
if (cmd_buffer_) {
cmd_buffer_.bindDescriptorSets(bind_point, layout, first_set,
static_cast<u32>(descriptor_sets.size()),
descriptor_sets.data(),
static_cast<u32>(dynamic_offsets.size()),
dynamic_offsets.data());
}
}
void render_context::push_constants(vk::ShaderStageFlags stage_flags, u32 offset, std::span<const u8> data) {
if (cmd_buffer_ && current_pipeline_layout_ && !data.empty()) {
cmd_buffer_.pushConstants(current_pipeline_layout_, stage_flags, offset,
static_cast<u32>(data.size()), data.data());
}
}
void render_context::set_viewport(f32 x, f32 y, f32 width, f32 height) {
if (cmd_buffer_) {
vk::Viewport viewport{};
viewport.x = x;
viewport.y = y;
viewport.width = width;
viewport.height = height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
cmd_buffer_.setViewport(0, 1, &viewport);
}
}
void render_context::set_scissor(i32 x, i32 y, u32 width, u32 height) {
if (cmd_buffer_) {
vk::Rect2D scissor{};
scissor.offset = vk::Offset2D{x, y};
scissor.extent = vk::Extent2D{width, height};
cmd_buffer_.setScissor(0, 1, &scissor);
}
}
void render_context::draw(u32 vertex_count, u32 instance_count, u32 first_vertex, u32 first_instance) {
if (cmd_buffer_) {
cmd_buffer_.draw(vertex_count, instance_count, first_vertex, first_instance);
}
}
void render_context::draw_indexed(u32 index_count, u32 instance_count,
u32 first_index, i32 vertex_offset, u32 first_instance) {
if (cmd_buffer_) {
cmd_buffer_.drawIndexed(index_count, instance_count, first_index, vertex_offset, first_instance);
}
}
void render_context::draw_fullscreen_quad() {
// 绘制一个覆盖整个视口的三角形
// 顶点: (-1, -1), (3, -1), (-1, 3)
// 这样只需要一个三角形就能覆盖整个屏幕
static const std::array<vec2, 3> vertices = {
vec2{-1.0f, -1.0f},
vec2{3.0f, -1.0f},
vec2{-1.0f, 3.0f}
};
// 这里需要先绑定顶点缓冲,暂时使用简单的绘制
// 实际使用时应通过批处理渲染器或预创建的几何体
draw(3, 1, 0, 0);
}
void render_context::transition_image_layout(vk::Image image,
vk::ImageLayout old_layout,
vk::ImageLayout new_layout,
vk::ImageAspectFlags aspect_mask) {
if (!cmd_buffer_) return;
vk::ImageMemoryBarrier2 barrier{};
barrier.oldLayout = old_layout;
barrier.newLayout = new_layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image;
barrier.subresourceRange.aspectMask = aspect_mask;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
// 设置源和目标阶段/访问掩码
if (old_layout == vk::ImageLayout::eUndefined &&
new_layout == vk::ImageLayout::eColorAttachmentOptimal) {
barrier.srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe;
barrier.srcAccessMask = vk::AccessFlagBits2::eNone;
barrier.dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput;
barrier.dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite;
} else if (old_layout == vk::ImageLayout::eColorAttachmentOptimal &&
new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) {
barrier.srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput;
barrier.srcAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite;
barrier.dstStageMask = vk::PipelineStageFlagBits2::eFragmentShader;
barrier.dstAccessMask = vk::AccessFlagBits2::eShaderRead;
} else {
barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
barrier.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite;
barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
barrier.dstAccessMask = vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
}
vk::DependencyInfo dependency_info{};
dependency_info.imageMemoryBarrierCount = 1;
dependency_info.pImageMemoryBarriers = &barrier;
cmd_buffer_.pipelineBarrier2(&dependency_info);
}
void render_context::image_barrier(const image_memory_barrier_config& config) {
if (!cmd_buffer_) return;
vk::ImageMemoryBarrier2 barrier{};
barrier.oldLayout = config.old_layout;
barrier.newLayout = config.new_layout;
barrier.srcQueueFamilyIndex = config.src_queue_family_index;
barrier.dstQueueFamilyIndex = config.dst_queue_family_index;
barrier.image = config.image;
barrier.subresourceRange = config.subresource_range;
vk::DependencyInfo dependency_info{};
dependency_info.imageMemoryBarrierCount = 1;
dependency_info.pImageMemoryBarriers = &barrier;
cmd_buffer_.pipelineBarrier2(&dependency_info);
}
} // namespace mirai

View File

@@ -0,0 +1,312 @@
/**
* @file render_context.hpp
* @brief MIRAI 框架渲染上下文
* @author MIRAI Team
* @version 0.1.0
*
* 提供渲染时访问 Vulkan 资源的接口,用于自定义渲染控件。
*/
#pragma once
#include "vulkan_types.hpp"
#include "renderer.hpp"
#include "pipeline.hpp"
#include "command_buffer.hpp"
#include <memory>
#include <functional>
namespace mirai {
// ================================================================================================
// 前置声明
// ================================================================================================
class render_context;
// ================================================================================================
// 渲染阶段
// ================================================================================================
/**
* @brief 渲染阶段类型
*/
enum class render_phase {
/// 正常渲染阶段
normal,
/// 后效处理阶段backdrop
backdrop,
/// 遮罩阶段
mask
};
// ================================================================================================
// 渲染上下文
// ================================================================================================
/**
* @brief 渲染上下文
*
* 提供渲染时访问 Vulkan 资源的接口。
* 控件可以通过此上下文获取渲染器、命令缓冲等进行自定义渲染。
*
* @example
* @code
* void my_widget::on_paint(paint_context& ctx) {
* auto* rc = ctx.get_render_context();
* if (rc) {
* auto cmd = rc->get_command_buffer();
* // 自定义渲染命令
* }
* }
* @endcode
*/
class render_context {
public:
/**
* @brief 构造函数
* @param renderer 渲染器
* @param cmd_buffer 命令缓冲
* @param phase 当前渲染阶段
*/
render_context(renderer* renderer, vk::CommandBuffer cmd_buffer, render_phase phase = render_phase::normal);
/**
* @brief 析构函数
*/
~render_context();
// 禁止拷贝
render_context(const render_context&) = delete;
render_context& operator=(const render_context&) = delete;
// ============================================================================================
// 资源访问
// ============================================================================================
/**
* @brief 获取渲染器
* @return 渲染器指针
*/
[[nodiscard]] renderer* get_renderer() const noexcept { return renderer_; }
/**
* @brief 获取命令缓冲
* @return Vulkan 命令缓冲
*/
[[nodiscard]] vk::CommandBuffer get_command_buffer() const noexcept { return cmd_buffer_; }
/**
* @brief 获取渲染器
* @return 渲染器引用
*/
[[nodiscard]] renderer& get_renderer_ref() const { return *renderer_; }
/**
* @brief 获取批处理渲染器
* @return 批处理渲染器指针
*/
[[nodiscard]] batch_renderer* get_batch_renderer() const;
/**
* @brief 获取当前帧索引
* @return 帧索引
*/
[[nodiscard]] u32 get_frame_index() const;
/**
* @brief 获取当前图像索引
* @return 图像索引
*/
[[nodiscard]] u32 get_image_index() const;
/**
* @brief 获取交换链范围
* @return 交换链范围
*/
[[nodiscard]] vk::Extent2D get_swapchain_extent() const;
/**
* @brief 获取交换链格式
* @return 交换链格式
*/
[[nodiscard]] vk::Format get_swapchain_format() const;
/**
* @brief 获取深度格式
* @return 深度格式
*/
[[nodiscard]] vk::Format get_depth_format() const;
// ============================================================================================
// 渲染阶段管理
// ============================================================================================
/**
* @brief 获取当前渲染阶段
* @return 渲染阶段
*/
[[nodiscard]] render_phase get_phase() const noexcept { return phase_; }
/**
* @brief 检查是否为后效处理阶段
* @return 如果是后效处理阶段返回 true
*/
[[nodiscard]] bool is_backdrop_phase() const noexcept { return phase_ == render_phase::backdrop; }
/**
* @brief 检查是否为遮罩阶段
* @return 如果是遮罩阶段返回 true
*/
[[nodiscard]] bool is_mask_phase() const noexcept { return phase_ == render_phase::mask; }
// ============================================================================================
// 渲染操作
// ============================================================================================
/**
* @brief 绑定图形管线
* @param pipeline 管线句柄
*/
void bind_pipeline(vk::Pipeline pipeline);
/**
* @brief 绑定管线布局
* @param layout 管线布局
*/
void bind_pipeline_layout(vk::PipelineLayout layout);
/**
* @brief 绑定描述符集
* @param first_set 第一个集索引
* @param descriptor_sets 描述符集列表
* @param dynamic_offsets 动态偏移量列表
*/
void bind_descriptor_sets(vk::PipelineBindPoint bind_point,
vk::PipelineLayout layout,
u32 first_set,
std::span<const vk::DescriptorSet> descriptor_sets,
std::span<const u32> dynamic_offsets = {});
/**
* @brief 推送常量
* @param stage_flags 着色器阶段标志
* @param offset 偏移量
* @param data 数据
*/
void push_constants(vk::ShaderStageFlags stage_flags, u32 offset, std::span<const u8> data);
/**
* @brief 推送常量(模板版本)
* @tparam T 数据类型
* @param stage_flags 着色器阶段标志
* @param data 数据
* @param offset 偏移量
*/
template<typename T>
void push_constants(vk::ShaderStageFlags stage_flags, const T& data, u32 offset = 0) {
push_constants(stage_flags, offset, std::as_bytes(std::span(&data, 1)));
}
/**
* @brief 设置视口
* @param x X 坐标
* @param y Y 坐标
* @param width 宽度
* @param height 高度
*/
void set_viewport(f32 x, f32 y, f32 width, f32 height);
/**
* @brief 设置裁剪矩形
* @param x X 坐标
* @param y Y 坐标
* @param width 宽度
* @param height 高度
*/
void set_scissor(i32 x, i32 y, u32 width, u32 height);
/**
* @brief 绘制
* @param vertex_count 顶点数量
* @param instance_count 实例数量
* @param first_vertex 第一个顶点
* @param first_instance 第一个实例
*/
void draw(u32 vertex_count, u32 instance_count = 1, u32 first_vertex = 0, u32 first_instance = 0);
/**
* @brief 索引绘制
* @param index_count 索引数量
* @param instance_count 实例数量
* @param first_index 第一个索引
* @param vertex_offset 顶点偏移
* @param first_instance 第一个实例
*/
void draw_indexed(u32 index_count, u32 instance_count = 1,
u32 first_index = 0, i32 vertex_offset = 0, u32 first_instance = 0);
/**
* @brief 绘制全屏四边形
*/
void draw_fullscreen_quad();
// ============================================================================================
// 图像操作
// ============================================================================================
/**
* @brief 转换图像布局
* @param image 图像
* @param old_layout 旧布局
* @param new_layout 新布局
* @param aspect_mask 方面掩码
*/
void transition_image_layout(vk::Image image,
vk::ImageLayout old_layout,
vk::ImageLayout new_layout,
vk::ImageAspectFlags aspect_mask = vk::ImageAspectFlagBits::eColor);
/**
* @barrier 图像内存屏障
* @param config 屏障配置
*/
void image_barrier(const image_memory_barrier_config& config);
private:
/// 渲染器
renderer* renderer_ = nullptr;
/// 命令缓冲
vk::CommandBuffer cmd_buffer_ = VK_NULL_HANDLE;
/// 当前渲染阶段
render_phase phase_ = render_phase::normal;
/// 当前绑定的管线布局
vk::PipelineLayout current_pipeline_layout_ = VK_NULL_HANDLE;
};
// ================================================================================================
// 绘制上下文扩展
// ================================================================================================
/**
* @brief 绘制上下文扩展接口
*
* 提供获取渲染上下文的能力。
*/
class paint_context_ex {
public:
/**
* @brief 获取渲染上下文
* @return 渲染上下文指针,如果不可用返回 nullptr
*/
[[nodiscard]] virtual render_context* get_render_context() const = 0;
protected:
~paint_context_ex() = default;
};
} // namespace mirai

View File

@@ -5,6 +5,10 @@
* @version 0.1.0
*/
// 定义动态分发加载器的存储(必须在包含 vulkan.hpp 之前,且只能在一个编译单元中定义)
#include "vulkan_types.hpp"
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
#include "vulkan_instance.hpp"
#include "logger.hpp"

View File

@@ -6,6 +6,15 @@
project(mirai_shader)
simple_library(STATIC)
target_link_libraries(${PROJECT_NAME} PUBLIC mirai_core mirai_render mirai_resource Vulkan::Vulkan)
# 添加 GLSL 着色器编译到现有目标
# 自动搜索 shaders/glsl 目录下的所有 .vert/.frag 文件
add_glsl_shader_library(TARGET ${PROJECT_NAME}
SHADER_PATH shaders/glsl
INCLUDE_DIRS shaders/glsl
RECURSIVE
)
set_target_properties(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
@@ -18,4 +27,4 @@ install(TARGETS ${PROJECT_NAME}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
message(STATUS "Configured mirai_shader library")
message(STATUS "Configured mirai_shader library with GLSL shaders")

View File

@@ -1,710 +0,0 @@
/**
* @file shader_cache.cpp
* @brief MIRAI 框架着色器缓存系统实现
* @author MIRAI Team
* @version 0.1.0
*/
#include "shader_cache.hpp"
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <sstream>
namespace mirai {
namespace {
/**
* @brief 简单的 FNV-1a 哈希函数
*/
constexpr u64 fnv1a_hash(const char* data, size_t length) {
constexpr u64 FNV_OFFSET = 14695981039346656037ULL;
constexpr u64 FNV_PRIME = 1099511628211ULL;
u64 hash = FNV_OFFSET;
for (size_t i = 0; i < length; ++i) {
hash ^= static_cast<u64>(static_cast<u8>(data[i]));
hash *= FNV_PRIME;
}
return hash;
}
/**
* @brief 将哈希值转换为十六进制字符串
*/
std::string hash_to_hex(u64 hash) {
std::ostringstream oss;
oss << std::hex << std::setfill('0') << std::setw(16) << hash;
return oss.str();
}
/**
* @brief 缓存文件魔数
*/
constexpr u32 CACHE_MAGIC = 0x4D494C43; // "MILC"
/**
* @brief 缓存文件头
*/
struct cache_file_header {
u32 magic{CACHE_MAGIC};
u32 version{1};
u32 spirv_size{0};
u32 stage{0};
u64 options_hash{0};
u64 created_timestamp{0};
u32 entry_point_length{0};
u32 reserved{0};
};
} // anonymous namespace
// ================================================================================================
// shader_cache 实现
// ================================================================================================
shader_cache::shader_cache() : config_() {}
shader_cache::shader_cache(const shader_cache_config& config) : config_(config) {
if (config_.enable_disk_cache && config_.load_on_startup) {
load_from_disk();
}
}
shader_cache::~shader_cache() {
if (config_.enable_disk_cache && config_.save_on_shutdown) {
save_to_disk();
}
}
shader_cache::shader_cache(shader_cache&& other) noexcept
: config_(std::move(other.config_))
, memory_cache_(std::move(other.memory_cache_))
, stats_(other.stats_) {}
shader_cache& shader_cache::operator=(shader_cache&& other) noexcept {
if (this != &other) {
config_ = std::move(other.config_);
memory_cache_ = std::move(other.memory_cache_);
stats_ = other.stats_;
}
return *this;
}
std::optional<shader_cache_entry> shader_cache::get(
std::string_view source,
shader_stage stage,
const shader_compile_options& options) {
std::string key = generate_key(source, stage, options);
return get_by_key(key);
}
std::optional<shader_cache_entry> shader_cache::get_by_key(std::string_view key) {
std::lock_guard<std::mutex> lock(mutex_);
// 先在内存缓存中查找
if (config_.enable_memory_cache) {
auto it = memory_cache_.find(std::string(key));
if (it != memory_cache_.end()) {
if (!is_expired(it->second)) {
update_access_info(it->second);
++stats_.hits;
// 构造返回值reflection 需要重新执行(因为 shader_reflection 不可拷贝)
shader_cache_entry result;
result.metadata = it->second.metadata;
result.spirv = it->second.spirv;
result.valid = it->second.valid;
if (!result.spirv.empty()) {
(void)result.reflection.reflect(result.spirv);
}
return result;
} else {
// 过期,移除
memory_cache_.erase(it);
}
}
}
// 在磁盘缓存中查找
if (config_.enable_disk_cache) {
auto entry = load_entry_from_disk(key);
if (entry.has_value() && !is_expired(*entry)) {
++stats_.hits;
// 加载到内存缓存(移动进去)
if (config_.enable_memory_cache) {
memory_cache_.insert_or_assign(std::string(key), std::move(*entry));
// 从缓存返回副本
auto& cached = memory_cache_.at(std::string(key));
shader_cache_entry result;
result.metadata = cached.metadata;
result.spirv = cached.spirv;
result.valid = cached.valid;
if (!result.spirv.empty()) {
(void)result.reflection.reflect(result.spirv);
}
return result;
}
return entry;
}
}
++stats_.misses;
return std::nullopt;
}
void shader_cache::put(
std::string_view source,
shader_stage stage,
const shader_compile_options& options,
const shader_compile_result& result) {
if (!result.success || result.spirv.empty()) {
return;
}
std::string key = generate_key(source, stage, options);
shader_cache_entry entry;
entry.metadata.key = key;
entry.metadata.stage = stage;
entry.metadata.entry_point = options.entry_point;
entry.metadata.created_at = std::chrono::system_clock::now();
entry.metadata.last_accessed = entry.metadata.created_at;
entry.metadata.access_count = 1;
entry.metadata.spirv_size = result.spirv.size() * sizeof(u32);
entry.metadata.options_hash = hash_options(options);
entry.metadata.dependencies = result.dependencies;
entry.spirv = result.spirv;
// 从 spirv 重新执行反射shader_reflection 不可拷贝)
if (!entry.spirv.empty()) {
(void)entry.reflection.reflect(entry.spirv);
}
entry.valid = true;
std::lock_guard<std::mutex> lock(mutex_);
// 检查是否需要清理
if (config_.max_entries > 0 && memory_cache_.size() >= config_.max_entries) {
cleanup_to_fit();
}
// 存入内存缓存
if (config_.enable_memory_cache) {
memory_cache_.emplace(key, std::move(entry));
++stats_.entry_count;
stats_.total_size += result.spirv.size() * sizeof(u32);
}
// 存入磁盘缓存
if (config_.enable_disk_cache) {
save_entry_to_disk(entry);
}
}
shader_compile_result shader_cache::get_or_compile(
std::string_view source,
shader_stage stage,
const shader_compile_options& options,
shader_compiler& compiler) {
// 尝试从缓存获取
auto cached = get(source, stage, options);
if (cached) {
shader_compile_result result;
result.success = true;
result.spirv = std::move(cached->spirv);
result.reflection = std::move(cached->reflection);
result.dependencies = std::move(cached->metadata.dependencies);
return result;
}
// 编译
auto result = compiler.compile_from_source(source, stage, options);
// 缓存成功的结果
if (result.success) {
put(source, stage, options, result);
}
return result;
}
bool shader_cache::invalidate(std::string_view key) {
std::lock_guard<std::mutex> lock(mutex_);
bool removed = false;
// 从内存缓存移除
auto it = memory_cache_.find(std::string(key));
if (it != memory_cache_.end()) {
stats_.total_size -= it->second.metadata.spirv_size;
--stats_.entry_count;
memory_cache_.erase(it);
removed = true;
}
// 从磁盘缓存移除
if (config_.enable_disk_cache) {
auto path = get_cache_file_path(key);
if (std::filesystem::exists(path)) {
std::filesystem::remove(path);
removed = true;
}
}
return removed;
}
u32 shader_cache::invalidate(const std::vector<std::string>& keys) {
u32 count = 0;
for (const auto& key : keys) {
if (invalidate(key)) {
++count;
}
}
return count;
}
u32 shader_cache::invalidate_by_path(const std::filesystem::path& path) {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::string> keys_to_remove;
for (const auto& [key, entry] : memory_cache_) {
// 检查源文件路径
if (entry.metadata.source_path == path) {
keys_to_remove.push_back(key);
continue;
}
// 检查依赖
for (const auto& dep : entry.metadata.dependencies) {
if (dep == path) {
keys_to_remove.push_back(key);
break;
}
}
}
u32 count = 0;
for (const auto& key : keys_to_remove) {
auto it = memory_cache_.find(key);
if (it != memory_cache_.end()) {
stats_.total_size -= it->second.metadata.spirv_size;
--stats_.entry_count;
memory_cache_.erase(it);
++count;
// 从磁盘移除
if (config_.enable_disk_cache) {
auto cache_path = get_cache_file_path(key);
if (std::filesystem::exists(cache_path)) {
std::filesystem::remove(cache_path);
}
}
}
}
return count;
}
void shader_cache::clear() {
std::lock_guard<std::mutex> lock(mutex_);
memory_cache_.clear();
stats_.entry_count = 0;
stats_.total_size = 0;
// 清空磁盘缓存
if (config_.enable_disk_cache && std::filesystem::exists(config_.cache_directory)) {
std::filesystem::remove_all(config_.cache_directory);
}
}
bool shader_cache::contains(
std::string_view source,
shader_stage stage,
const shader_compile_options& options) const {
std::string key = generate_key(source, stage, options);
return contains_key(key);
}
bool shader_cache::contains_key(std::string_view key) const {
std::lock_guard<std::mutex> lock(mutex_);
if (config_.enable_memory_cache) {
if (memory_cache_.find(std::string(key)) != memory_cache_.end()) {
return true;
}
}
if (config_.enable_disk_cache) {
auto path = get_cache_file_path(key);
return std::filesystem::exists(path);
}
return false;
}
shader_cache_stats shader_cache::get_stats() const {
std::lock_guard<std::mutex> lock(mutex_);
shader_cache_stats current_stats = stats_;
current_stats.entry_count = static_cast<u32>(memory_cache_.size());
current_stats.total_size = calculate_memory_usage();
// 计算磁盘大小
if (config_.enable_disk_cache && std::filesystem::exists(config_.cache_directory)) {
current_stats.disk_size = 0;
for (const auto& entry : std::filesystem::directory_iterator(config_.cache_directory)) {
if (entry.is_regular_file()) {
current_stats.disk_size += entry.file_size();
}
}
}
return current_stats;
}
std::vector<std::string> shader_cache::get_keys() const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::string> keys;
keys.reserve(memory_cache_.size());
for (const auto& [key, entry] : memory_cache_) {
keys.push_back(key);
}
return keys;
}
std::optional<shader_cache_entry_metadata> shader_cache::get_metadata(std::string_view key) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = memory_cache_.find(std::string(key));
if (it != memory_cache_.end()) {
return it->second.metadata;
}
return std::nullopt;
}
bool shader_cache::save_to_disk() {
if (!config_.enable_disk_cache) {
return false;
}
std::lock_guard<std::mutex> lock(mutex_);
// 确保缓存目录存在
if (!std::filesystem::exists(config_.cache_directory)) {
std::filesystem::create_directories(config_.cache_directory);
}
bool all_success = true;
for (const auto& [key, entry] : memory_cache_) {
if (!save_entry_to_disk(entry)) {
all_success = false;
}
}
return all_success;
}
u32 shader_cache::load_from_disk() {
if (!config_.enable_disk_cache) {
return 0;
}
if (!std::filesystem::exists(config_.cache_directory)) {
return 0;
}
std::lock_guard<std::mutex> lock(mutex_);
u32 loaded = 0;
for (const auto& file_entry : std::filesystem::directory_iterator(config_.cache_directory)) {
if (!file_entry.is_regular_file()) {
continue;
}
std::string filename = file_entry.path().filename().string();
if (filename.size() < 4 || filename.substr(filename.size() - 4) != ".spv") {
continue;
}
std::string key = filename.substr(0, filename.size() - 4);
auto entry = load_entry_from_disk(key);
if (entry.has_value() && !is_expired(entry.value())) {
memory_cache_.insert_or_assign(key, std::move(entry.value()));
++loaded;
}
}
stats_.entry_count = static_cast<u32>(memory_cache_.size());
stats_.total_size = calculate_memory_usage();
return loaded;
}
u32 shader_cache::cleanup_expired() {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::string> expired_keys;
for (const auto& [key, entry] : memory_cache_) {
if (is_expired(entry)) {
expired_keys.push_back(key);
}
}
for (const auto& key : expired_keys) {
auto it = memory_cache_.find(key);
if (it != memory_cache_.end()) {
stats_.total_size -= it->second.metadata.spirv_size;
memory_cache_.erase(it);
}
if (config_.enable_disk_cache) {
auto path = get_cache_file_path(key);
if (std::filesystem::exists(path)) {
std::filesystem::remove(path);
}
}
}
stats_.entry_count = static_cast<u32>(memory_cache_.size());
return static_cast<u32>(expired_keys.size());
}
u32 shader_cache::cleanup_to_fit() {
// 清理过期条目
u32 cleaned = cleanup_expired();
// 检查条目数限制
if (config_.max_entries > 0 && memory_cache_.size() > config_.max_entries) {
u32 to_remove = static_cast<u32>(memory_cache_.size()) - config_.max_entries;
cleaned += evict_entries(to_remove);
}
// 检查大小限制
if (config_.max_memory_size > 0) {
size_t current_size = calculate_memory_usage();
if (current_size > config_.max_memory_size) {
cleaned += evict_entries(config_.max_memory_size);
}
}
return cleaned;
}
void shader_cache::set_config(const shader_cache_config& config) {
std::lock_guard<std::mutex> lock(mutex_);
config_ = config;
}
std::string shader_cache::generate_key(
std::string_view source,
shader_stage stage,
const shader_compile_options& options) {
// 组合源码、阶段和选项生成唯一键
u64 source_hash = hash_string(source);
u64 options_hash = hash_options(options);
u64 combined = source_hash ^ (options_hash << 1) ^ (static_cast<u64>(stage) << 57);
return hash_to_hex(combined);
}
u64 shader_cache::hash_string(std::string_view str) {
return fnv1a_hash(str.data(), str.size());
}
u64 shader_cache::hash_options(const shader_compile_options& options) {
std::ostringstream oss;
oss << static_cast<int>(options.target_api);
oss << static_cast<int>(options.source_language);
oss << static_cast<int>(options.optimization);
oss << options.generate_debug_info;
oss << options.enable_16bit_types;
oss << options.entry_point;
oss << options.glsl_version;
oss << options.vulkan_semantics;
for (const auto& [name, value] : options.defines) {
oss << name << "=" << value << ";";
}
std::string combined = oss.str();
return hash_string(combined);
}
std::filesystem::path shader_cache::get_cache_file_path(std::string_view key) const {
return config_.cache_directory / (std::string(key) + ".spv");
}
bool shader_cache::save_entry_to_disk(const shader_cache_entry& entry) {
if (!config_.enable_disk_cache) {
return false;
}
// 确保目录存在
if (!std::filesystem::exists(config_.cache_directory)) {
std::filesystem::create_directories(config_.cache_directory);
}
auto path = get_cache_file_path(entry.metadata.key);
std::ofstream file(path, std::ios::binary);
if (!file) {
return false;
}
// 写入文件头
cache_file_header header;
header.spirv_size = static_cast<u32>(entry.spirv.size());
header.stage = static_cast<u32>(entry.metadata.stage);
header.options_hash = entry.metadata.options_hash;
header.created_timestamp = static_cast<u64>(
std::chrono::duration_cast<std::chrono::seconds>(
entry.metadata.created_at.time_since_epoch()
).count()
);
header.entry_point_length = static_cast<u32>(entry.metadata.entry_point.size());
file.write(reinterpret_cast<const char*>(&header), sizeof(header));
// 写入入口点
file.write(entry.metadata.entry_point.data(), entry.metadata.entry_point.size());
// 写入 SPIR-V
file.write(reinterpret_cast<const char*>(entry.spirv.data()),
entry.spirv.size() * sizeof(u32));
return true;
}
std::optional<shader_cache_entry> shader_cache::load_entry_from_disk(std::string_view key) {
auto path = get_cache_file_path(key);
if (!std::filesystem::exists(path)) {
return std::nullopt;
}
std::ifstream file(path, std::ios::binary);
if (!file) {
return std::nullopt;
}
// 读取文件头
cache_file_header header;
file.read(reinterpret_cast<char*>(&header), sizeof(header));
if (header.magic != CACHE_MAGIC || header.version != config_.cache_version) {
return std::nullopt;
}
shader_cache_entry entry;
entry.metadata.key = std::string(key);
entry.metadata.stage = static_cast<shader_stage>(header.stage);
entry.metadata.options_hash = header.options_hash;
entry.metadata.created_at = std::chrono::system_clock::time_point(
std::chrono::seconds(header.created_timestamp)
);
entry.metadata.last_accessed = std::chrono::system_clock::now();
entry.metadata.spirv_size = header.spirv_size * sizeof(u32);
// 读取入口点
entry.metadata.entry_point.resize(header.entry_point_length);
file.read(entry.metadata.entry_point.data(), header.entry_point_length);
// 读取 SPIR-V
entry.spirv.resize(header.spirv_size);
file.read(reinterpret_cast<char*>(entry.spirv.data()),
header.spirv_size * sizeof(u32));
// 执行反射(处理 [[nodiscard]] 返回值)
bool reflect_success = entry.reflection.reflect(entry.spirv);
entry.valid = reflect_success && entry.reflection.is_valid();
return entry;
}
void shader_cache::update_access_info(shader_cache_entry& entry) {
entry.metadata.last_accessed = std::chrono::system_clock::now();
++entry.metadata.access_count;
}
bool shader_cache::is_expired(const shader_cache_entry& entry) const {
if (config_.expiration_seconds == 0) {
return false;
}
auto now = std::chrono::system_clock::now();
auto age = std::chrono::duration_cast<std::chrono::seconds>(
now - entry.metadata.created_at
);
return age.count() > config_.expiration_seconds;
}
size_t shader_cache::calculate_memory_usage() const {
size_t total = 0;
for (const auto& [key, entry] : memory_cache_) {
total += entry.metadata.spirv_size;
total += key.size();
total += entry.metadata.entry_point.size();
}
return total;
}
u32 shader_cache::evict_entries(size_t target) {
if (memory_cache_.empty()) {
return 0;
}
// 按最后访问时间排序
std::vector<std::pair<std::string, std::chrono::system_clock::time_point>> entries;
entries.reserve(memory_cache_.size());
for (const auto& [key, entry] : memory_cache_) {
entries.emplace_back(key, entry.metadata.last_accessed);
}
std::ranges::sort(entries, [](const auto& a, const auto& b) {
return a.second < b.second; // 最旧的在前
});
u32 evicted = 0;
size_t current_size = calculate_memory_usage();
for (const auto& [key, time] : entries) {
if (config_.max_entries > 0 && memory_cache_.size() <= config_.max_entries &&
(config_.max_memory_size == 0 || current_size <= config_.max_memory_size)) {
break;
}
auto it = memory_cache_.find(key);
if (it != memory_cache_.end()) {
current_size -= it->second.metadata.spirv_size;
memory_cache_.erase(it);
++evicted;
}
}
stats_.entry_count = static_cast<u32>(memory_cache_.size());
stats_.total_size = current_size;
return evicted;
}
} // namespace mirai

View File

@@ -1,441 +0,0 @@
/**
* @file shader_cache.hpp
* @brief MIRAI 框架着色器缓存系统
* @author MIRAI Team
* @version 0.1.0
*
* @details
* 本文件实现了着色器缓存系统,提供:
* - 编译后 SPIR-V 的内存缓存
* - 磁盘持久化
* - 基于源码哈希的缓存键
* - 缓存版本控制
*/
#pragma once
#include "shader_types.hpp"
#include "shader_compiler.hpp"
#include <chrono>
#include <filesystem>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace mirai {
/**
* @brief 缓存条目元数据
*/
struct shader_cache_entry_metadata {
/// 缓存键(源码哈希)
std::string key;
/// 着色器阶段
shader_stage stage{shader_stage::vertex};
/// 入口点
std::string entry_point{"main"};
/// 源文件路径
std::filesystem::path source_path;
/// 依赖文件列表
std::vector<std::filesystem::path> dependencies;
/// 创建时间
std::chrono::system_clock::time_point created_at;
/// 最后访问时间
std::chrono::system_clock::time_point last_accessed;
/// 访问次数
u32 access_count{0};
/// SPIR-V 大小(字节)
size_t spirv_size{0};
/// 编译选项哈希
u64 options_hash{0};
};
/**
* @brief 着色器缓存条目
*/
struct shader_cache_entry {
/// 元数据
shader_cache_entry_metadata metadata;
/// SPIR-V 字节码
std::vector<u32> spirv;
/// 反射信息
shader_reflection reflection;
/// 是否有效
bool valid{false};
};
/**
* @brief 缓存统计信息
*/
struct shader_cache_stats {
/// 缓存命中次数
u64 hits{0};
/// 缓存未命中次数
u64 misses{0};
/// 当前缓存条目数
u32 entry_count{0};
/// 缓存总大小(字节)
size_t total_size{0};
/// 磁盘缓存大小(字节)
size_t disk_size{0};
/**
* @brief 获取命中率
* @return 命中率0-1
*/
[[nodiscard]] f64 get_hit_rate() const noexcept {
u64 total = hits + misses;
return (total > 0) ? static_cast<f64>(hits) / static_cast<f64>(total) : 0.0;
}
};
/**
* @brief 缓存配置
*/
struct shader_cache_config {
/// 是否启用内存缓存
bool enable_memory_cache{true};
/// 是否启用磁盘缓存
bool enable_disk_cache{true};
/// 磁盘缓存目录
std::filesystem::path cache_directory{"shader_cache"};
/// 最大内存缓存大小字节0表示无限制
size_t max_memory_size{0};
/// 最大磁盘缓存大小字节0表示无限制
size_t max_disk_size{0};
/// 最大缓存条目数0表示无限制
u32 max_entries{0};
/// 缓存版本(用于使旧缓存失效)
u32 cache_version{1};
/// 是否在启动时加载磁盘缓存
bool load_on_startup{true};
/// 是否在关闭时保存到磁盘
bool save_on_shutdown{true};
/// 条目过期时间0表示永不过期
u32 expiration_seconds{0};
};
/**
* @brief 着色器缓存
*
* @details
* 提供着色器编译结果的缓存功能,支持内存缓存和磁盘持久化。
*
* 使用示例:
* @code
* shader_cache_config config;
* config.cache_directory = "my_cache";
* config.enable_disk_cache = true;
*
* shader_cache cache(config);
*
* // 尝试从缓存获取
* auto entry = cache.get(source_code, stage, options);
* if (entry) {
* // 使用缓存的 SPIR-V
* } else {
* // 编译并缓存
* auto result = compiler.compile_from_source(source_code, stage, options);
* cache.put(source_code, stage, options, result);
* }
*
* // 或使用 get_or_compile
* auto result = cache.get_or_compile(source_code, stage, options, compiler);
* @endcode
*/
class shader_cache {
public:
/**
* @brief 默认构造函数
*/
shader_cache();
/**
* @brief 带配置的构造函数
* @param config 缓存配置
*/
explicit shader_cache(const shader_cache_config& config);
/**
* @brief 析构函数
*/
~shader_cache();
// 禁用拷贝
shader_cache(const shader_cache&) = delete;
shader_cache& operator=(const shader_cache&) = delete;
// 允许移动
shader_cache(shader_cache&& other) noexcept;
shader_cache& operator=(shader_cache&& other) noexcept;
/**
* @brief 从缓存获取条目
* @param source 着色器源码
* @param stage 着色器阶段
* @param options 编译选项
* @return 缓存条目,未找到返回空
*/
[[nodiscard]] std::optional<shader_cache_entry> get(
std::string_view source,
shader_stage stage,
const shader_compile_options& options = {}
);
/**
* @brief 按键获取缓存条目
* @param key 缓存键
* @return 缓存条目,未找到返回空
*/
[[nodiscard]] std::optional<shader_cache_entry> get_by_key(std::string_view key);
/**
* @brief 存入缓存
* @param source 着色器源码
* @param stage 着色器阶段
* @param options 编译选项
* @param result 编译结果
*/
void put(
std::string_view source,
shader_stage stage,
const shader_compile_options& options,
const shader_compile_result& result
);
/**
* @brief 获取或编译
* @param source 着色器源码
* @param stage 着色器阶段
* @param options 编译选项
* @param compiler 编译器引用
* @return 编译结果
*/
[[nodiscard]] shader_compile_result get_or_compile(
std::string_view source,
shader_stage stage,
const shader_compile_options& options,
shader_compiler& compiler
);
/**
* @brief 使缓存条目失效
* @param key 缓存键
* @return 是否成功
*/
bool invalidate(std::string_view key);
/**
* @brief 使多个缓存条目失效
* @param keys 缓存键列表
* @return 失效的条目数
*/
u32 invalidate(const std::vector<std::string>& keys);
/**
* @brief 根据源文件路径使缓存失效
* @param path 源文件路径
* @return 失效的条目数
*/
u32 invalidate_by_path(const std::filesystem::path& path);
/**
* @brief 清空所有缓存
*/
void clear();
/**
* @brief 检查是否有缓存
* @param source 着色器源码
* @param stage 着色器阶段
* @param options 编译选项
* @return 是否有缓存
*/
[[nodiscard]] bool contains(
std::string_view source,
shader_stage stage,
const shader_compile_options& options = {}
) const;
/**
* @brief 按键检查是否有缓存
* @param key 缓存键
* @return 是否有缓存
*/
[[nodiscard]] bool contains_key(std::string_view key) const;
/**
* @brief 获取缓存统计信息
* @return 统计信息
*/
[[nodiscard]] shader_cache_stats get_stats() const;
/**
* @brief 获取所有缓存键
* @return 键列表
*/
[[nodiscard]] std::vector<std::string> get_keys() const;
/**
* @brief 获取缓存条目元数据
* @param key 缓存键
* @return 元数据,未找到返回空
*/
[[nodiscard]] std::optional<shader_cache_entry_metadata> get_metadata(std::string_view key) const;
/**
* @brief 保存缓存到磁盘
* @return 是否成功
*/
bool save_to_disk();
/**
* @brief 从磁盘加载缓存
* @return 加载的条目数
*/
u32 load_from_disk();
/**
* @brief 清理过期条目
* @return 清理的条目数
*/
u32 cleanup_expired();
/**
* @brief 清理以满足大小限制
* @return 清理的条目数
*/
u32 cleanup_to_fit();
/**
* @brief 获取配置
* @return 配置引用
*/
[[nodiscard]] const shader_cache_config& get_config() const noexcept { return config_; }
/**
* @brief 设置配置
* @param config 新配置
*/
void set_config(const shader_cache_config& config);
/**
* @brief 生成缓存键
* @param source 着色器源码
* @param stage 着色器阶段
* @param options 编译选项
* @return 缓存键
*/
[[nodiscard]] static std::string generate_key(
std::string_view source,
shader_stage stage,
const shader_compile_options& options
);
/**
* @brief 计算字符串哈希
* @param str 字符串
* @return 哈希值
*/
[[nodiscard]] static u64 hash_string(std::string_view str);
/**
* @brief 计算编译选项哈希
* @param options 编译选项
* @return 哈希值
*/
[[nodiscard]] static u64 hash_options(const shader_compile_options& options);
private:
/// 配置
shader_cache_config config_;
/// 内存缓存
std::unordered_map<std::string, shader_cache_entry> memory_cache_;
/// 统计信息
mutable shader_cache_stats stats_;
/// 互斥锁
mutable std::mutex mutex_;
/**
* @brief 获取磁盘缓存文件路径
* @param key 缓存键
* @return 文件路径
*/
[[nodiscard]] std::filesystem::path get_cache_file_path(std::string_view key) const;
/**
* @brief 保存条目到磁盘
* @param entry 缓存条目
* @return 是否成功
*/
bool save_entry_to_disk(const shader_cache_entry& entry);
/**
* @brief 从磁盘加载条目
* @param key 缓存键
* @return 缓存条目,失败返回空
*/
[[nodiscard]] std::optional<shader_cache_entry> load_entry_from_disk(std::string_view key);
/**
* @brief 更新访问信息
* @param entry 缓存条目
*/
void update_access_info(shader_cache_entry& entry);
/**
* @brief 检查条目是否过期
* @param entry 缓存条目
* @return 是否过期
*/
[[nodiscard]] bool is_expired(const shader_cache_entry& entry) const;
/**
* @brief 计算当前内存使用量
* @return 内存使用量(字节)
*/
[[nodiscard]] size_t calculate_memory_usage() const;
/**
* @brief 淘汰条目LRU策略
* @param target_size 目标大小
* @return 淘汰的条目数
*/
u32 evict_entries(size_t target_size);
};
} // namespace mirai

View File

@@ -1,501 +0,0 @@
/**
* @file shader_compiler.hpp
* @brief MIRAI 框架着色器编译器封装
* @author MIRAI Team
* @version 0.1.0
*
* @details
* 本文件实现了 Slang 着色器编译器的封装,提供:
* - GLSL/HLSL/Slang 源码编译为 SPIR-V
* - 预处理器支持宏定义、include 处理)
* - 编译错误报告(行号、列号、消息)
* - SPIR-V 验证
* - 条件编译支持(当 Slang 不可用时使用 glslang
*/
#pragma once
#include "shader_types.hpp"
#include "shader_reflection.hpp"
#include <filesystem>
#include <functional>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace mirai {
/**
* @brief 着色器源语言
*/
enum class shader_source_language : u8 {
glsl, ///< GLSL 着色器语言
hlsl, ///< HLSL 着色器语言
slang, ///< Slang 着色器语言
spirv, ///< 预编译的 SPIR-V
auto_detect ///< 自动检测语言
};
/**
* @brief 着色器目标 API
*/
enum class shader_target_api : u8 {
vulkan_1_0, ///< Vulkan 1.0
vulkan_1_1, ///< Vulkan 1.1
vulkan_1_2, ///< Vulkan 1.2
vulkan_1_3 ///< Vulkan 1.3(默认)
};
/**
* @brief 编译优化级别
*/
enum class shader_optimization_level : u8 {
none, ///< 无优化(用于调试)
size, ///< 优化代码大小
performance ///< 优化性能(默认)
};
/**
* @brief 着色器编译选项
*/
struct shader_compile_options {
/// 目标 API
shader_target_api target_api{shader_target_api::vulkan_1_3};
/// 源语言
shader_source_language source_language{shader_source_language::auto_detect};
/// 优化级别
shader_optimization_level optimization{shader_optimization_level::performance};
/// 是否生成调试信息
bool generate_debug_info{false};
/// 是否启用 16 位类型
bool enable_16bit_types{false};
/// 是否启用行指令
bool enable_line_directives{true};
/// 预处理器宏定义 (name -> value)
std::unordered_map<std::string, std::string> defines;
/// include 搜索路径
std::vector<std::filesystem::path> include_paths;
/// 入口点名称(默认为 "main"
std::string entry_point{"main"};
/// GLSL 版本(用于 GLSL 源码)
u32 glsl_version{460};
/// 是否启用 Vulkan 语义
bool vulkan_semantics{true};
/// 是否自动绑定资源
bool auto_bind_resources{false};
/// 是否启用行列主序矩阵
bool row_major_matrices{false};
/// 是否启用严格模式
bool strict_mode{false};
/// 是否将警告视为错误
bool warnings_as_errors{false};
/**
* @brief 添加宏定义
* @param name 宏名称
* @param value 宏值(可选)
* @return 自身引用,支持链式调用
*/
shader_compile_options& define(const std::string& name, const std::string& value = "1") {
defines[name] = value;
return *this;
}
/**
* @brief 添加 include 路径
* @param path include 搜索路径
* @return 自身引用,支持链式调用
*/
shader_compile_options& add_include_path(const std::filesystem::path& path) {
include_paths.push_back(path);
return *this;
}
/**
* @brief 设置入口点
* @param name 入口点名称
* @return 自身引用,支持链式调用
*/
shader_compile_options& set_entry_point(const std::string& name) {
entry_point = name;
return *this;
}
/**
* @brief 启用调试模式
* @return 自身引用,支持链式调用
*/
shader_compile_options& enable_debug() {
generate_debug_info = true;
optimization = shader_optimization_level::none;
return *this;
}
};
/**
* @brief 着色器编译结果
*/
struct shader_compile_result {
/// 是否编译成功
bool success{false};
/// SPIR-V 字节码
std::vector<u32> spirv;
/// 反射信息
shader_reflection reflection;
/// 编译诊断信息(错误和警告)
std::vector<compile_diagnostic> diagnostics;
/// 依赖文件列表(包含的文件)
std::vector<std::filesystem::path> dependencies;
/// 预处理后的源码(可选,用于调试)
std::optional<std::string> preprocessed_source;
/**
* @brief 检查是否有错误
* @return 是否有错误
*/
[[nodiscard]] bool has_errors() const noexcept {
return std::ranges::any_of(diagnostics, [](const auto& d) {
return d.severity == diagnostic_severity::error;
});
}
/**
* @brief 检查是否有警告
* @return 是否有警告
*/
[[nodiscard]] bool has_warnings() const noexcept {
return std::ranges::any_of(diagnostics, [](const auto& d) {
return d.severity == diagnostic_severity::warning;
});
}
/**
* @brief 获取所有错误
* @return 错误列表
*/
[[nodiscard]] std::vector<compile_diagnostic> get_errors() const {
std::vector<compile_diagnostic> errors;
for (const auto& d : diagnostics) {
if (d.severity == diagnostic_severity::error) {
errors.push_back(d);
}
}
return errors;
}
/**
* @brief 获取所有警告
* @return 警告列表
*/
[[nodiscard]] std::vector<compile_diagnostic> get_warnings() const {
std::vector<compile_diagnostic> warnings;
for (const auto& d : diagnostics) {
if (d.severity == diagnostic_severity::warning) {
warnings.push_back(d);
}
}
return warnings;
}
/**
* @brief 获取格式化的诊断信息字符串
* @return 诊断信息字符串
*/
[[nodiscard]] std::string get_diagnostics_string() const;
};
/**
* @brief Include 文件解析器回调类型
* @param include_name 要包含的文件名
* @param requesting_source 请求包含的源文件路径
* @param is_system_include 是否为系统 include使用 <> 而非 ""
* @return 包含文件的内容和完整路径,或空表示找不到
*/
using include_resolver = std::function<std::optional<std::pair<std::string, std::filesystem::path>>(
std::string_view include_name,
const std::filesystem::path& requesting_source,
bool is_system_include
)>;
/**
* @brief 着色器编译器
*
* @details
* 封装了 Slang/glslang 编译器,提供统一的着色器编译接口。
* 支持 GLSL、HLSL、Slang 源码编译为 SPIR-V。
*
* 使用示例:
* @code
* shader_compiler compiler;
*
* // 设置 include 解析器
* compiler.set_include_resolver([](auto name, auto path, auto sys) {
* // 自定义 include 解析逻辑
* return std::nullopt;
* });
*
* // 编译 GLSL 源码
* shader_compile_options options;
* options.define("USE_NORMAL_MAP", "1");
*
* auto result = compiler.compile_from_source(
* glsl_source,
* shader_stage::vertex,
* options
* );
*
* if (result.success) {
* // 使用 result.spirv 和 result.reflection
* }
* @endcode
*/
class shader_compiler {
public:
/**
* @brief 默认构造函数
*/
shader_compiler();
/**
* @brief 析构函数
*/
~shader_compiler();
// 禁用拷贝
shader_compiler(const shader_compiler&) = delete;
shader_compiler& operator=(const shader_compiler&) = delete;
// 允许移动
shader_compiler(shader_compiler&& other) noexcept;
shader_compiler& operator=(shader_compiler&& other) noexcept;
/**
* @brief 从源码编译着色器
* @param source 着色器源码
* @param stage 着色器阶段
* @param options 编译选项
* @return 编译结果
*/
[[nodiscard]] shader_compile_result compile_from_source(
std::string_view source,
shader_stage stage,
const shader_compile_options& options = {}
);
/**
* @brief 从文件编译着色器
* @param path 着色器文件路径
* @param stage 着色器阶段
* @param options 编译选项
* @return 编译结果
*/
[[nodiscard]] shader_compile_result compile_from_file(
const std::filesystem::path& path,
shader_stage stage,
const shader_compile_options& options = {}
);
/**
* @brief 编译 Slang 着色器
* @param source Slang 源码
* @param entry_point 入口点名称
* @param stage 着色器阶段
* @param options 编译选项
* @return 编译结果
*/
[[nodiscard]] shader_compile_result compile_slang(
std::string_view source,
std::string_view entry_point,
shader_stage stage,
const shader_compile_options& options = {}
);
/**
* @brief 预处理着色器源码
* @param source 原始源码
* @param options 预处理选项
* @return 预处理后的源码,失败返回空
*/
[[nodiscard]] std::optional<std::string> preprocess(
std::string_view source,
const shader_compile_options& options = {}
);
/**
* @brief 验证 SPIR-V 字节码
* @param spirv SPIR-V 字节码
* @return 验证结果,包含任何错误或警告
*/
[[nodiscard]] shader_compile_result validate_spirv(std::span<const u32> spirv);
/**
* @brief 优化 SPIR-V 字节码
* @param spirv 原始 SPIR-V 字节码
* @param level 优化级别
* @return 优化后的 SPIR-V失败返回空
*/
[[nodiscard]] std::optional<std::vector<u32>> optimize_spirv(
std::span<const u32> spirv,
shader_optimization_level level = shader_optimization_level::performance
);
/**
* @brief 反汇编 SPIR-V 为可读文本
* @param spirv SPIR-V 字节码
* @return 反汇编文本,失败返回空
*/
[[nodiscard]] std::optional<std::string> disassemble_spirv(std::span<const u32> spirv);
/**
* @brief 设置 include 解析器
* @param resolver include 解析器回调
*/
void set_include_resolver(include_resolver resolver);
/**
* @brief 获取默认 include 解析器
* @param search_paths 搜索路径列表
* @return 默认 include 解析器
*/
[[nodiscard]] static include_resolver make_default_include_resolver(
const std::vector<std::filesystem::path>& search_paths
);
/**
* @brief 从文件扩展名推断着色器阶段
* @param path 文件路径
* @return 推断的着色器阶段,无法推断返回空
*/
[[nodiscard]] static std::optional<shader_stage> infer_stage_from_extension(
const std::filesystem::path& path
);
/**
* @brief 从文件扩展名推断源语言
* @param path 文件路径
* @return 推断的源语言
*/
[[nodiscard]] static shader_source_language infer_language_from_extension(
const std::filesystem::path& path
);
/**
* @brief 检查 Slang 编译器是否可用
* @return Slang 是否可用
*/
[[nodiscard]] static bool is_slang_available() noexcept;
/**
* @brief 获取支持的着色器文件扩展名列表
* @return 扩展名列表
*/
[[nodiscard]] static std::vector<std::string> get_supported_extensions();
private:
/// 前向声明实现类
struct impl;
std::unique_ptr<impl> impl_;
/// include 解析器
include_resolver include_resolver_;
/**
* @brief 内部编译函数
*/
[[nodiscard]] shader_compile_result compile_internal(
std::string_view source,
shader_stage stage,
const std::filesystem::path& source_path,
const shader_compile_options& options
);
/**
* @brief 检测源语言
*/
[[nodiscard]] shader_source_language detect_language(
std::string_view source,
const std::filesystem::path& path
) const;
/**
* @brief 使用 glslang 编译 GLSL
*/
[[nodiscard]] shader_compile_result compile_glsl(
std::string_view source,
shader_stage stage,
const std::filesystem::path& source_path,
const shader_compile_options& options
);
/**
* @brief 使用 Slang 编译
*/
[[nodiscard]] shader_compile_result compile_with_slang(
std::string_view source,
shader_stage stage,
const std::filesystem::path& source_path,
const shader_compile_options& options
);
/**
* @brief 处理 include 指令
*/
[[nodiscard]] std::optional<std::string> resolve_includes(
std::string_view source,
const std::filesystem::path& source_path,
const shader_compile_options& options,
std::vector<std::filesystem::path>& dependencies,
std::unordered_set<std::string>& included_files
);
};
/**
* @brief 着色器编译器工厂
*
* @details
* 提供创建和管理着色器编译器实例的工厂方法。
*/
class shader_compiler_factory {
public:
/**
* @brief 获取全局编译器实例
* @return 编译器引用
*/
[[nodiscard]] static shader_compiler& get_instance();
/**
* @brief 创建新的编译器实例
* @return 新编译器实例
*/
[[nodiscard]] static std::unique_ptr<shader_compiler> create();
// 禁止实例化
shader_compiler_factory() = delete;
};
} // namespace mirai

View File

@@ -1,529 +0,0 @@
/**
* @file shader_hot_reload.cpp
* @brief MIRAI 框架着色器热重载系统实现
* @author MIRAI Team
* @version 0.1.0
*/
#include "shader_hot_reload.hpp"
#include "shader_compiler.hpp"
#include "shader_library.hpp"
#include <algorithm>
#include <regex>
namespace mirai {
// ================================================================================================
// shader_hot_reload 实现
// ================================================================================================
shader_hot_reload::shader_hot_reload() : config_() {
last_check_time_ = std::chrono::system_clock::now();
}
shader_hot_reload::shader_hot_reload(const hot_reload_config& config) : config_(config) {
last_check_time_ = std::chrono::system_clock::now();
}
shader_hot_reload::~shader_hot_reload() {
stop_background_thread();
}
bool shader_hot_reload::watch(const std::filesystem::path& path, std::string_view shader_name) {
std::lock_guard<std::mutex> lock(mutex_);
if (!std::filesystem::exists(path)) {
return false;
}
std::string normalized = normalize_path(path);
if (std::filesystem::is_directory(path)) {
// 监视目录
scan_directory(path);
watch_entry entry;
entry.path = path;
entry.is_directory = true;
entry.last_modified = std::filesystem::last_write_time(path);
watch_entries_[normalized] = entry;
} else {
// 监视单个文件
if (!matches_extension(path)) {
return false;
}
watch_entry entry;
entry.path = path;
entry.is_directory = false;
entry.last_modified = std::filesystem::last_write_time(path);
entry.file_size = std::filesystem::file_size(path);
if (!shader_name.empty()) {
entry.dependents.push_back(std::string(shader_name));
shader_sources_[std::string(shader_name)] = path;
}
watch_entries_[normalized] = entry;
}
return true;
}
bool shader_hot_reload::unwatch(const std::filesystem::path& path) {
std::lock_guard<std::mutex> lock(mutex_);
std::string normalized = normalize_path(path);
auto it = watch_entries_.find(normalized);
if (it == watch_entries_.end()) {
return false;
}
// 如果是目录,移除所有子文件
if (it->second.is_directory) {
std::vector<std::string> to_remove;
for (const auto& [key, entry] : watch_entries_) {
if (!entry.is_directory && entry.path.string().starts_with(path.string())) {
to_remove.push_back(key);
}
}
for (const auto& key : to_remove) {
watch_entries_.erase(key);
}
}
watch_entries_.erase(it);
return true;
}
void shader_hot_reload::unwatch_all() {
std::lock_guard<std::mutex> lock(mutex_);
watch_entries_.clear();
shader_sources_.clear();
file_dependents_.clear();
}
void shader_hot_reload::add_dependency(std::string_view shader_name, const std::filesystem::path& dependency_path) {
std::lock_guard<std::mutex> lock(mutex_);
std::string normalized = normalize_path(dependency_path);
file_dependents_[normalized].insert(std::string(shader_name));
// 如果文件还没被监视,添加监视
if (watch_entries_.find(normalized) == watch_entries_.end()) {
if (std::filesystem::exists(dependency_path)) {
watch_entry entry;
entry.path = dependency_path;
entry.is_directory = false;
entry.last_modified = std::filesystem::last_write_time(dependency_path);
entry.file_size = std::filesystem::file_size(dependency_path);
entry.dependents.push_back(std::string(shader_name));
watch_entries_[normalized] = entry;
}
} else {
// 添加到依赖列表
auto& dependents = watch_entries_[normalized].dependents;
if (std::find(dependents.begin(), dependents.end(), shader_name) == dependents.end()) {
dependents.push_back(std::string(shader_name));
}
}
}
void shader_hot_reload::remove_dependency(std::string_view shader_name, const std::filesystem::path& dependency_path) {
std::lock_guard<std::mutex> lock(mutex_);
std::string normalized = normalize_path(dependency_path);
auto it = file_dependents_.find(normalized);
if (it != file_dependents_.end()) {
it->second.erase(std::string(shader_name));
if (it->second.empty()) {
file_dependents_.erase(it);
}
}
auto entry_it = watch_entries_.find(normalized);
if (entry_it != watch_entries_.end()) {
auto& dependents = entry_it->second.dependents;
dependents.erase(
std::remove(dependents.begin(), dependents.end(), shader_name),
dependents.end()
);
}
}
void shader_hot_reload::clear_dependencies(std::string_view shader_name) {
std::lock_guard<std::mutex> lock(mutex_);
std::string name(shader_name);
for (auto& [path, dependents] : file_dependents_) {
dependents.erase(name);
}
for (auto& [path, entry] : watch_entries_) {
auto& dependents = entry.dependents;
dependents.erase(
std::remove(dependents.begin(), dependents.end(), name),
dependents.end()
);
}
}
std::vector<std::filesystem::path> shader_hot_reload::get_dependencies(std::string_view shader_name) const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::filesystem::path> result;
for (const auto& [path, dependents] : file_dependents_) {
if (dependents.find(std::string(shader_name)) != dependents.end()) {
result.push_back(path);
}
}
return result;
}
void shader_hot_reload::on_shader_changed(shader_change_callback callback) {
std::lock_guard<std::mutex> lock(callback_mutex_);
shader_callbacks_.push_back(std::move(callback));
}
void shader_hot_reload::on_file_changed(file_change_callback callback) {
std::lock_guard<std::mutex> lock(callback_mutex_);
file_callbacks_.push_back(std::move(callback));
}
u32 shader_hot_reload::check_for_changes() {
if (!enabled_.load()) {
return 0;
}
std::lock_guard<std::mutex> lock(mutex_);
auto now = std::chrono::system_clock::now();
u32 change_count = 0;
std::vector<file_change_event> events;
for (auto& [path_str, entry] : watch_entries_) {
if (entry.is_directory) {
// 对于目录,检查新文件
if (config_.recursive) {
for (const auto& dir_entry : std::filesystem::recursive_directory_iterator(entry.path)) {
if (dir_entry.is_regular_file() && matches_extension(dir_entry.path())) {
std::string file_path = normalize_path(dir_entry.path());
if (watch_entries_.find(file_path) == watch_entries_.end()) {
// 新文件
file_change_event event;
event.path = dir_entry.path();
event.type = file_change_type::created;
event.timestamp = now;
events.push_back(event);
// 添加到监视
watch_entry new_entry;
new_entry.path = dir_entry.path();
new_entry.is_directory = false;
new_entry.last_modified = std::filesystem::last_write_time(dir_entry.path());
new_entry.file_size = std::filesystem::file_size(dir_entry.path());
watch_entries_[file_path] = new_entry;
}
}
}
}
continue;
}
if (!std::filesystem::exists(entry.path)) {
// 文件被删除
file_change_event event;
event.path = entry.path;
event.type = file_change_type::deleted;
event.timestamp = now;
events.push_back(event);
continue;
}
auto current_time = std::filesystem::last_write_time(entry.path);
auto current_size = std::filesystem::file_size(entry.path);
if (current_time != entry.last_modified || current_size != entry.file_size) {
// 文件被修改
file_change_event event;
event.path = entry.path;
event.type = file_change_type::modified;
event.timestamp = now;
events.push_back(event);
entry.last_modified = current_time;
entry.file_size = current_size;
}
}
// 处理事件
for (const auto& event : events) {
process_file_change(event);
++change_count;
}
last_check_time_ = now;
return change_count;
}
void shader_hot_reload::enable() {
enabled_.store(true);
}
void shader_hot_reload::disable() {
enabled_.store(false);
}
void shader_hot_reload::start_background_thread() {
if (background_thread_running_.load()) {
return;
}
should_stop_.store(false);
background_thread_running_.store(true);
background_thread_ = std::make_unique<std::thread>(&shader_hot_reload::background_thread_func, this);
}
void shader_hot_reload::stop_background_thread() {
if (!background_thread_running_.load()) {
return;
}
should_stop_.store(true);
if (background_thread_ && background_thread_->joinable()) {
background_thread_->join();
}
background_thread_.reset();
background_thread_running_.store(false);
}
bool shader_hot_reload::force_reload(std::string_view shader_name) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = shader_sources_.find(std::string(shader_name));
if (it == shader_sources_.end()) {
return false;
}
trigger_reload(shader_name, it->second);
return true;
}
u32 shader_hot_reload::force_reload_all() {
std::lock_guard<std::mutex> lock(mutex_);
u32 count = 0;
for (const auto& [name, path] : shader_sources_) {
trigger_reload(name, path);
++count;
}
return count;
}
u32 shader_hot_reload::get_watch_count() const {
std::lock_guard<std::mutex> lock(mutex_);
return static_cast<u32>(watch_entries_.size());
}
std::vector<std::filesystem::path> shader_hot_reload::get_watched_paths() const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::filesystem::path> paths;
paths.reserve(watch_entries_.size());
for (const auto& [path_str, entry] : watch_entries_) {
paths.push_back(entry.path);
}
return paths;
}
void shader_hot_reload::set_config(const hot_reload_config& config) {
std::lock_guard<std::mutex> lock(mutex_);
config_ = config;
}
bool shader_hot_reload::matches_extension(const std::filesystem::path& path) const {
auto ext = path.extension().string();
std::ranges::transform(ext, ext.begin(), ::tolower);
for (const auto& watch_ext : config_.watch_extensions) {
if (ext == watch_ext) {
return true;
}
}
return false;
}
bool shader_hot_reload::should_ignore(const std::filesystem::path& path) const {
std::string filename = path.filename().string();
for (const auto& pattern : config_.ignore_patterns) {
// 简单的模式匹配
if (pattern.starts_with("*")) {
// 后缀匹配
std::string suffix = pattern.substr(1);
if (filename.ends_with(suffix)) {
return true;
}
} else if (pattern.ends_with("*")) {
// 前缀匹配
std::string prefix = pattern.substr(0, pattern.size() - 1);
if (filename.starts_with(prefix)) {
return true;
}
} else {
// 精确匹配
if (filename == pattern) {
return true;
}
}
}
return false;
}
void shader_hot_reload::background_thread_func() {
while (!should_stop_.load()) {
if (enabled_.load()) {
check_for_changes();
}
std::this_thread::sleep_for(std::chrono::milliseconds(config_.poll_interval_ms));
}
}
void shader_hot_reload::scan_directory(const std::filesystem::path& directory) {
if (!std::filesystem::exists(directory) || !std::filesystem::is_directory(directory)) {
return;
}
auto iterator = config_.recursive
? std::filesystem::recursive_directory_iterator(directory)
: std::filesystem::recursive_directory_iterator(directory, std::filesystem::directory_options::none);
for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) {
if (!entry.is_regular_file()) {
continue;
}
if (!matches_extension(entry.path())) {
continue;
}
if (should_ignore(entry.path())) {
continue;
}
std::string normalized = normalize_path(entry.path());
if (watch_entries_.find(normalized) == watch_entries_.end()) {
watch_entry new_entry;
new_entry.path = entry.path();
new_entry.is_directory = false;
new_entry.last_modified = std::filesystem::last_write_time(entry.path());
new_entry.file_size = std::filesystem::file_size(entry.path());
watch_entries_[normalized] = new_entry;
}
}
}
void shader_hot_reload::process_file_change(const file_change_event& event) {
// 通知文件变化
notify_file_change(event);
if (!config_.auto_recompile) {
return;
}
std::string normalized = normalize_path(event.path);
// 查找依赖此文件的着色器
std::unordered_set<std::string> affected_shaders;
auto entry_it = watch_entries_.find(normalized);
if (entry_it != watch_entries_.end()) {
for (const auto& dep : entry_it->second.dependents) {
affected_shaders.insert(dep);
}
}
auto dep_it = file_dependents_.find(normalized);
if (dep_it != file_dependents_.end()) {
affected_shaders.insert(dep_it->second.begin(), dep_it->second.end());
}
// 触发重载
for (const auto& shader_name : affected_shaders) {
auto source_it = shader_sources_.find(shader_name);
if (source_it != shader_sources_.end()) {
trigger_reload(shader_name, source_it->second);
}
}
}
void shader_hot_reload::trigger_reload(std::string_view shader_name, const std::filesystem::path& source_path) {
shader_reload_event event;
event.name = std::string(shader_name);
event.source_path = source_path;
event.timestamp = std::chrono::system_clock::now();
// 尝试重新编译
if (compiler_ != nullptr) {
auto stage = shader_compiler::infer_stage_from_extension(source_path);
if (stage) {
auto result = compiler_->compile_from_file(source_path, *stage);
event.success = result.success;
if (!result.success) {
event.error_message = result.get_diagnostics_string();
}
} else {
event.success = false;
event.error_message = "Could not infer shader stage from file extension";
}
} else {
event.success = false;
event.error_message = "No compiler available";
}
notify_shader_reload(event);
}
void shader_hot_reload::notify_file_change(const file_change_event& event) {
std::lock_guard<std::mutex> lock(callback_mutex_);
for (const auto& callback : file_callbacks_) {
if (callback) {
callback(event);
}
}
}
void shader_hot_reload::notify_shader_reload(const shader_reload_event& event) {
std::lock_guard<std::mutex> lock(callback_mutex_);
for (const auto& callback : shader_callbacks_) {
if (callback) {
callback(event);
}
}
}
std::string shader_hot_reload::normalize_path(const std::filesystem::path& path) {
return std::filesystem::weakly_canonical(path).string();
}
} // namespace mirai

View File

@@ -1,463 +0,0 @@
/**
* @file shader_hot_reload.hpp
* @brief MIRAI 框架着色器热重载系统
* @author MIRAI Team
* @version 0.1.0
*
* @details
* 本文件实现了着色器热重载系统,提供:
* - 文件系统监视
* - 自动重新编译变化的着色器
* - 依赖追踪include 文件)
* - 回调通知机制
*/
#pragma once
#include "shader_types.hpp"
#include <atomic>
#include <chrono>
#include <filesystem>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace mirai {
// 前向声明
class shader_compiler;
class shader_module;
class shader_library;
/**
* @brief 文件变化类型
*/
enum class file_change_type : u8 {
created, ///< 文件创建
modified, ///< 文件修改
deleted ///< 文件删除
};
/**
* @brief 文件变化事件
*/
struct file_change_event {
/// 文件路径
std::filesystem::path path;
/// 变化类型
file_change_type type{file_change_type::modified};
/// 变化时间
std::chrono::system_clock::time_point timestamp;
};
/**
* @brief 着色器重载事件
*/
struct shader_reload_event {
/// 着色器名称
std::string name;
/// 源文件路径
std::filesystem::path source_path;
/// 是否重载成功
bool success{false};
/// 错误信息(如果失败)
std::string error_message;
/// 新的着色器模块(如果成功)
shader_module* new_module{nullptr};
/// 重载时间
std::chrono::system_clock::time_point timestamp;
};
/**
* @brief 着色器变化回调类型
*/
using shader_change_callback = std::function<void(const shader_reload_event&)>;
/**
* @brief 文件变化回调类型
*/
using file_change_callback = std::function<void(const file_change_event&)>;
/**
* @brief 热重载配置
*/
struct hot_reload_config {
/// 是否启用热重载
bool enabled{true};
/// 轮询间隔(毫秒)
u32 poll_interval_ms{100};
/// 防抖时间(毫秒)- 文件变化后等待此时间再触发
u32 debounce_ms{200};
/// 是否自动重新编译
bool auto_recompile{true};
/// 是否监视 include 文件
bool watch_includes{true};
/// 是否递归监视目录
bool recursive{true};
/// 要监视的文件扩展名
std::vector<std::string> watch_extensions{".vert", ".frag", ".comp", ".geom", ".tesc", ".tese", ".glsl", ".hlsl", ".slang"};
/// 要忽略的文件/目录模式
std::vector<std::string> ignore_patterns{".*", "_*", "*.bak", "*.tmp"};
};
/**
* @brief 监视条目信息
*/
struct watch_entry {
/// 文件路径
std::filesystem::path path;
/// 最后修改时间
std::filesystem::file_time_type last_modified;
/// 文件大小
uintmax_t file_size{0};
/// 是否是目录
bool is_directory{false};
/// 依赖此文件的着色器列表
std::vector<std::string> dependents;
};
/**
* @brief 着色器热重载管理器
*
* @details
* 监视着色器文件变化并自动触发重新编译。
*
* 使用示例:
* @code
* hot_reload_config config;
* config.poll_interval_ms = 100;
*
* shader_hot_reload hot_reload(config);
*
* // 注册变化回调
* hot_reload.on_shader_changed([](const shader_reload_event& event) {
* if (event.success) {
* std::cout << "Shader reloaded: " << event.name << std::endl;
* } else {
* std::cerr << "Reload failed: " << event.error_message << std::endl;
* }
* });
*
* // 监视目录
* hot_reload.watch("shaders/");
*
* // 启动监视
* hot_reload.enable();
*
* // 在主循环中调用
* while (running) {
* hot_reload.check_for_changes();
* // ...
* }
*
* // 停止监视
* hot_reload.disable();
* @endcode
*/
class shader_hot_reload {
public:
/**
* @brief 默认构造函数
*/
shader_hot_reload();
/**
* @brief 带配置的构造函数
* @param config 热重载配置
*/
explicit shader_hot_reload(const hot_reload_config& config);
/**
* @brief 析构函数
*/
~shader_hot_reload();
// 禁用拷贝
shader_hot_reload(const shader_hot_reload&) = delete;
shader_hot_reload& operator=(const shader_hot_reload&) = delete;
// 禁用移动(包含线程)
shader_hot_reload(shader_hot_reload&&) = delete;
shader_hot_reload& operator=(shader_hot_reload&&) = delete;
/**
* @brief 监视文件或目录
* @param path 文件或目录路径
* @param shader_name 关联的着色器名称(可选)
* @return 是否成功
*/
bool watch(const std::filesystem::path& path, std::string_view shader_name = "");
/**
* @brief 取消监视
* @param path 文件或目录路径
* @return 是否成功
*/
bool unwatch(const std::filesystem::path& path);
/**
* @brief 取消所有监视
*/
void unwatch_all();
/**
* @brief 添加依赖关系
* @param shader_name 着色器名称
* @param dependency_path 依赖文件路径
*/
void add_dependency(std::string_view shader_name, const std::filesystem::path& dependency_path);
/**
* @brief 移除依赖关系
* @param shader_name 着色器名称
* @param dependency_path 依赖文件路径
*/
void remove_dependency(std::string_view shader_name, const std::filesystem::path& dependency_path);
/**
* @brief 清除着色器的所有依赖
* @param shader_name 着色器名称
*/
void clear_dependencies(std::string_view shader_name);
/**
* @brief 获取着色器的依赖列表
* @param shader_name 着色器名称
* @return 依赖文件路径列表
*/
[[nodiscard]] std::vector<std::filesystem::path> get_dependencies(std::string_view shader_name) const;
/**
* @brief 设置着色器变化回调
* @param callback 回调函数
*/
void on_shader_changed(shader_change_callback callback);
/**
* @brief 设置文件变化回调
* @param callback 回调函数
*/
void on_file_changed(file_change_callback callback);
/**
* @brief 检查文件变化
* @return 变化的文件数量
*/
u32 check_for_changes();
/**
* @brief 启用热重载
*/
void enable();
/**
* @brief 禁用热重载
*/
void disable();
/**
* @brief 检查是否启用
* @return 是否启用
*/
[[nodiscard]] bool is_enabled() const noexcept { return enabled_.load(); }
/**
* @brief 启动后台监视线程
*/
void start_background_thread();
/**
* @brief 停止后台监视线程
*/
void stop_background_thread();
/**
* @brief 检查后台线程是否运行
* @return 是否运行
*/
[[nodiscard]] bool is_background_thread_running() const noexcept {
return background_thread_running_.load();
}
/**
* @brief 强制重载指定着色器
* @param shader_name 着色器名称
* @return 是否成功
*/
bool force_reload(std::string_view shader_name);
/**
* @brief 强制重载所有着色器
* @return 重载的着色器数量
*/
u32 force_reload_all();
/**
* @brief 获取监视的文件数量
* @return 文件数量
*/
[[nodiscard]] u32 get_watch_count() const;
/**
* @brief 获取所有监视的路径
* @return 路径列表
*/
[[nodiscard]] std::vector<std::filesystem::path> get_watched_paths() const;
/**
* @brief 获取配置
* @return 配置引用
*/
[[nodiscard]] const hot_reload_config& get_config() const noexcept { return config_; }
/**
* @brief 设置配置
* @param config 新配置
*/
void set_config(const hot_reload_config& config);
/**
* @brief 设置着色器编译器
* @param compiler 编译器指针
*/
void set_compiler(shader_compiler* compiler) { compiler_ = compiler; }
/**
* @brief 设置着色器库
* @param library 库指针
*/
void set_library(shader_library* library) { library_ = library; }
/**
* @brief 检查文件是否匹配监视扩展名
* @param path 文件路径
* @return 是否匹配
*/
[[nodiscard]] bool matches_extension(const std::filesystem::path& path) const;
/**
* @brief 检查路径是否应该被忽略
* @param path 路径
* @return 是否忽略
*/
[[nodiscard]] bool should_ignore(const std::filesystem::path& path) const;
private:
/// 配置
hot_reload_config config_;
/// 监视条目
std::unordered_map<std::string, watch_entry> watch_entries_;
/// 着色器名称到源文件的映射
std::unordered_map<std::string, std::filesystem::path> shader_sources_;
/// 文件到依赖它的着色器的映射
std::unordered_map<std::string, std::unordered_set<std::string>> file_dependents_;
/// 着色器变化回调列表
std::vector<shader_change_callback> shader_callbacks_;
/// 文件变化回调列表
std::vector<file_change_callback> file_callbacks_;
/// 待处理的变化事件
std::vector<file_change_event> pending_events_;
/// 上次检查时间
std::chrono::system_clock::time_point last_check_time_;
/// 编译器指针
shader_compiler* compiler_{nullptr};
/// 着色器库指针
shader_library* library_{nullptr};
/// 是否启用
std::atomic<bool> enabled_{false};
/// 后台线程是否运行
std::atomic<bool> background_thread_running_{false};
/// 是否应该停止
std::atomic<bool> should_stop_{false};
/// 后台线程
std::unique_ptr<std::thread> background_thread_;
/// 互斥锁
mutable std::mutex mutex_;
/// 回调互斥锁
mutable std::mutex callback_mutex_;
/**
* @brief 后台线程函数
*/
void background_thread_func();
/**
* @brief 扫描目录
* @param directory 目录路径
*/
void scan_directory(const std::filesystem::path& directory);
/**
* @brief 处理文件变化
* @param event 变化事件
*/
void process_file_change(const file_change_event& event);
/**
* @brief 触发着色器重载
* @param shader_name 着色器名称
* @param source_path 源文件路径
*/
void trigger_reload(std::string_view shader_name, const std::filesystem::path& source_path);
/**
* @brief 通知文件变化
* @param event 变化事件
*/
void notify_file_change(const file_change_event& event);
/**
* @brief 通知着色器重载
* @param event 重载事件
*/
void notify_shader_reload(const shader_reload_event& event);
/**
* @brief 规范化路径
* @param path 原始路径
* @return 规范化后的路径
*/
[[nodiscard]] static std::string normalize_path(const std::filesystem::path& path);
};
} // namespace mirai

View File

@@ -15,129 +15,33 @@ namespace mirai {
// shader_library 实现
// ================================================================================================
shader_library::shader_library(VkDevice device) : device_(device), config_() {
// 设置默认 include 解析器
compiler_.set_include_resolver(
shader_compiler::make_default_include_resolver(config_.search_paths)
);
}
shader_library::shader_library(VkDevice device, const shader_library_config& config)
: device_(device), config_(config) {
// 设置 include 解析器
compiler_.set_include_resolver(
shader_compiler::make_default_include_resolver(config_.search_paths)
);
// 初始化缓存
if (config_.enable_cache) {
cache_ = std::make_unique<shader_cache>(config_.cache_config);
}
// 初始化热重载
if (config_.enable_hot_reload) {
hot_reload_ = std::make_unique<shader_hot_reload>(config_.hot_reload_config);
hot_reload_->set_compiler(&compiler_);
hot_reload_->set_library(this);
// 注册热重载回调
hot_reload_->on_shader_changed([this](const shader_reload_event& event) {
on_hot_reload_event(event);
});
// 监视搜索路径
for (const auto& path : config_.search_paths) {
hot_reload_->watch(path);
}
hot_reload_->enable();
}
}
shader_library::shader_library(VkDevice device) : device_(device) {}
shader_library::~shader_library() {
if (hot_reload_) {
hot_reload_->disable();
hot_reload_->stop_background_thread();
}
unload_all();
}
shader_load_result shader_library::load(
shader_load_result shader_library::load_spirv_data(
std::string_view name,
const std::filesystem::path& path,
const shader_compile_options& options) {
// 尝试推断着色器阶段
auto stage = shader_compiler::infer_stage_from_extension(path);
if (!stage) {
shader_load_result result;
result.success = false;
result.error_message = "Could not infer shader stage from file extension: " + path.string();
return result;
}
return load(name, path, *stage, options);
}
shader_load_result shader_library::load(
std::string_view name,
const std::filesystem::path& path,
std::span<const u32> spirv,
shader_stage stage,
const shader_compile_options& options) {
// 查找文件
auto full_path = find_file(path);
if (!full_path) {
shader_load_result result;
result.success = false;
result.error_message = "File not found: " + path.string();
return result;
}
return load_internal(name, *full_path, stage, options);
}
shader_load_result shader_library::load_from_source(
std::string_view name,
std::string_view source,
shader_stage stage,
const shader_compile_options& options) {
std::string_view entry_point) {
std::lock_guard<std::mutex> lock(mutex_);
shader_load_result result;
// 合并编译选项
shader_compile_options merged_options = config_.default_compile_options;
merged_options.source_language = options.source_language;
merged_options.optimization = options.optimization;
merged_options.generate_debug_info = options.generate_debug_info;
merged_options.entry_point = options.entry_point;
for (const auto& [key, value] : options.defines) {
merged_options.defines[key] = value;
}
// 编译
shader_compile_result compile_result;
if (cache_) {
compile_result = cache_->get_or_compile(source, stage, merged_options, compiler_);
} else {
compile_result = compiler_.compile_from_source(source, stage, merged_options);
}
if (!compile_result.success) {
if (spirv.empty()) {
result.success = false;
result.error_message = compile_result.get_diagnostics_string();
result.error_message = "Empty SPIR-V data";
return result;
}
// 创建模块
shader_module_create_info create_info;
create_info.stage = stage;
create_info.spirv = std::move(compile_result.spirv);
create_info.entry_point = merged_options.entry_point;
create_info.spirv = std::vector<u32>(spirv.begin(), spirv.end());
create_info.entry_point = std::string(entry_point);
create_info.name = std::string(name);
auto module = shader_module::create(device_, create_info);
@@ -156,77 +60,11 @@ shader_load_result shader_library::load_from_source(
return result;
}
program_load_result shader_library::load_program(
shader_load_result shader_library::load_binary(
std::string_view name,
const std::filesystem::path& vertex_path,
const std::filesystem::path& fragment_path,
const shader_compile_options& options) {
const shader_binary& binary) {
program_load_result result;
// 生成着色器名称
std::string vs_name = std::string(name) + "_vs";
std::string fs_name = std::string(name) + "_fs";
// 加载顶点着色器
auto vs_result = load(vs_name, vertex_path, shader_stage::vertex, options);
if (!vs_result.success) {
result.success = false;
result.error_message = "Failed to load vertex shader: " + vs_result.error_message;
return result;
}
// 加载片段着色器
auto fs_result = load(fs_name, fragment_path, shader_stage::fragment, options);
if (!fs_result.success) {
result.success = false;
result.error_message = "Failed to load fragment shader: " + fs_result.error_message;
return result;
}
// 创建程序
return create_program(name, vs_name, fs_name);
}
program_load_result shader_library::load_compute_program(
std::string_view name,
const std::filesystem::path& compute_path,
const shader_compile_options& options) {
program_load_result result;
std::string cs_name = std::string(name) + "_cs";
// 加载计算着色器
auto cs_result = load(cs_name, compute_path, shader_stage::compute, options);
if (!cs_result.success) {
result.success = false;
result.error_message = "Failed to load compute shader: " + cs_result.error_message;
return result;
}
std::lock_guard<std::mutex> lock(mutex_);
// 创建计算程序
auto program = shader_program::create_compute(device_, modules_[cs_name].get(), name);
if (!program || !program->is_valid()) {
result.success = false;
result.error_message = "Failed to create compute program";
return result;
}
// 保存程序信息
std::string name_str(name);
program_info info;
info.compute_shader = cs_name;
info.compute_path = compute_path;
program_sources_[name_str] = info;
result.program = program.get();
programs_[name_str] = std::move(program);
result.success = true;
return result;
return load_spirv_data(name, binary.spirv, binary.stage, binary.entry_point);
}
program_load_result shader_library::create_program(
@@ -238,21 +76,25 @@ program_load_result shader_library::create_program(
program_load_result result;
auto* vs = get(vertex_name);
auto* fs = get(fragment_name);
if (!vs) {
// 查找着色器模块
auto vs_it = modules_.find(std::string(vertex_name));
if (vs_it == modules_.end()) {
result.success = false;
result.error_message = "Vertex shader not found: " + std::string(vertex_name);
return result;
}
if (!fs) {
auto fs_it = modules_.find(std::string(fragment_name));
if (fs_it == modules_.end()) {
result.success = false;
result.error_message = "Fragment shader not found: " + std::string(fragment_name);
return result;
}
auto* vs = vs_it->second.get();
auto* fs = fs_it->second.get();
// 创建程序
auto program = shader_program::create_graphics(device_, vs, fs, name);
if (!program || !program->is_valid()) {
result.success = false;
@@ -264,24 +106,43 @@ program_load_result shader_library::create_program(
return result;
}
// 保存程序信息
// 存储
std::string name_str(name);
program_info info;
info.vertex_shader = std::string(vertex_name);
info.fragment_shader = std::string(fragment_name);
result.program = program.get();
programs_[name_str] = std::move(program);
auto vs_it = module_sources_.find(std::string(vertex_name));
if (vs_it != module_sources_.end()) {
info.vertex_path = vs_it->second;
result.success = true;
return result;
}
program_load_result shader_library::create_compute_program(
std::string_view name,
std::string_view compute_name) {
std::lock_guard<std::mutex> lock(mutex_);
program_load_result result;
// 查找计算着色器模块
auto cs_it = modules_.find(std::string(compute_name));
if (cs_it == modules_.end()) {
result.success = false;
result.error_message = "Compute shader not found: " + std::string(compute_name);
return result;
}
auto fs_it = module_sources_.find(std::string(fragment_name));
if (fs_it != module_sources_.end()) {
info.fragment_path = fs_it->second;
auto* cs = cs_it->second.get();
// 创建程序
auto program = shader_program::create_compute(device_, cs, name);
if (!program || !program->is_valid()) {
result.success = false;
result.error_message = "Failed to create compute program";
return result;
}
program_sources_[name_str] = info;
// 存储
std::string name_str(name);
result.program = program.get();
programs_[name_str] = std::move(program);
@@ -330,12 +191,6 @@ bool shader_library::unload(std::string_view name) {
}
modules_.erase(it);
module_sources_.erase(name_str);
if (hot_reload_) {
hot_reload_->clear_dependencies(name);
}
return true;
}
@@ -350,8 +205,6 @@ bool shader_library::unload_program(std::string_view name) {
}
programs_.erase(it);
program_sources_.erase(name_str);
return true;
}
@@ -360,132 +213,6 @@ void shader_library::unload_all() {
programs_.clear();
modules_.clear();
module_sources_.clear();
program_sources_.clear();
}
shader_load_result shader_library::reload(std::string_view name) {
std::string name_str(name);
std::filesystem::path source_path;
shader_stage stage;
{
std::lock_guard<std::mutex> lock(mutex_);
auto source_it = module_sources_.find(name_str);
if (source_it == module_sources_.end()) {
shader_load_result result;
result.success = false;
result.error_message = "Shader not found or has no source path: " + name_str;
return result;
}
source_path = source_it->second;
auto module_it = modules_.find(name_str);
if (module_it != modules_.end()) {
stage = module_it->second->get_stage();
} else {
auto inferred = shader_compiler::infer_stage_from_extension(source_path);
if (!inferred) {
shader_load_result result;
result.success = false;
result.error_message = "Could not infer shader stage";
return result;
}
stage = *inferred;
}
// 先卸载
modules_.erase(name_str);
}
// 重新加载
auto result = load_internal(name, source_path, stage, config_.default_compile_options);
if (result.success) {
notify_shader_reload(name_str, result.module);
rebuild_dependent_programs(name_str);
}
return result;
}
program_load_result shader_library::reload_program(std::string_view name) {
std::string name_str(name);
program_info info;
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = program_sources_.find(name_str);
if (it == program_sources_.end()) {
program_load_result result;
result.success = false;
result.error_message = "Program not found: " + name_str;
return result;
}
info = it->second;
// 先卸载
programs_.erase(name_str);
}
program_load_result result;
if (!info.compute_shader.empty()) {
// 重新加载计算程序
result = load_compute_program(name, info.compute_path);
} else {
// 重新加载图形程序
result = load_program(name, info.vertex_path, info.fragment_path);
}
if (result.success) {
notify_program_reload(name_str, result.program);
}
return result;
}
u32 shader_library::reload_all() {
std::vector<std::string> shader_names;
std::vector<std::string> program_names;
{
std::lock_guard<std::mutex> lock(mutex_);
for (const auto& [name, path] : module_sources_) {
shader_names.push_back(name);
}
for (const auto& [name, info] : program_sources_) {
program_names.push_back(name);
}
}
u32 count = 0;
for (const auto& name : shader_names) {
auto result = reload(name);
if (result.success) {
++count;
}
}
for (const auto& name : program_names) {
auto result = reload_program(name);
if (result.success) {
++count;
}
}
return count;
}
void shader_library::update() {
if (hot_reload_ && hot_reload_->is_enabled()) {
hot_reload_->check_for_changes();
}
}
std::vector<std::string> shader_library::get_shader_names() const {
@@ -524,216 +251,4 @@ u32 shader_library::get_program_count() const {
return static_cast<u32>(programs_.size());
}
void shader_library::on_shader_reload(shader_reload_callback callback) {
std::lock_guard<std::mutex> lock(mutex_);
shader_reload_callbacks_.push_back(std::move(callback));
}
void shader_library::on_program_reload(program_reload_callback callback) {
std::lock_guard<std::mutex> lock(mutex_);
program_reload_callbacks_.push_back(std::move(callback));
}
void shader_library::add_search_path(const std::filesystem::path& path) {
std::lock_guard<std::mutex> lock(mutex_);
config_.search_paths.push_back(path);
// 更新 include 解析器
compiler_.set_include_resolver(
shader_compiler::make_default_include_resolver(config_.search_paths)
);
// 添加热重载监视
if (hot_reload_) {
hot_reload_->watch(path);
}
}
std::optional<std::filesystem::path> shader_library::find_file(const std::filesystem::path& filename) const {
// 先检查是否是绝对路径
if (filename.is_absolute() && std::filesystem::exists(filename)) {
return filename;
}
// 在搜索路径中查找
for (const auto& search_path : config_.search_paths) {
auto full_path = search_path / filename;
if (std::filesystem::exists(full_path)) {
return full_path;
}
}
// 相对于当前目录
if (std::filesystem::exists(filename)) {
return filename;
}
return std::nullopt;
}
void shader_library::enable_hot_reload() {
std::lock_guard<std::mutex> lock(mutex_);
if (!hot_reload_) {
hot_reload_ = std::make_unique<shader_hot_reload>(config_.hot_reload_config);
hot_reload_->set_compiler(&compiler_);
hot_reload_->set_library(this);
hot_reload_->on_shader_changed([this](const shader_reload_event& event) {
on_hot_reload_event(event);
});
for (const auto& path : config_.search_paths) {
hot_reload_->watch(path);
}
}
hot_reload_->enable();
}
void shader_library::disable_hot_reload() {
if (hot_reload_) {
hot_reload_->disable();
}
}
bool shader_library::is_hot_reload_enabled() const noexcept {
return hot_reload_ && hot_reload_->is_enabled();
}
shader_load_result shader_library::load_internal(
std::string_view name,
const std::filesystem::path& full_path,
shader_stage stage,
const shader_compile_options& options) {
std::lock_guard<std::mutex> lock(mutex_);
shader_load_result result;
// 合并编译选项
shader_compile_options merged_options = config_.default_compile_options;
merged_options.source_language = options.source_language;
merged_options.optimization = options.optimization;
merged_options.generate_debug_info = options.generate_debug_info;
if (!options.entry_point.empty()) {
merged_options.entry_point = options.entry_point;
}
for (const auto& [key, value] : options.defines) {
merged_options.defines[key] = value;
}
// 编译
auto compile_result = compiler_.compile_from_file(full_path, stage, merged_options);
if (!compile_result.success) {
result.success = false;
result.error_message = compile_result.get_diagnostics_string();
return result;
}
// 创建模块
shader_module_create_info create_info;
create_info.stage = stage;
create_info.spirv = std::move(compile_result.spirv);
create_info.entry_point = merged_options.entry_point;
create_info.name = std::string(name);
create_info.source_path = full_path;
auto module = shader_module::create(device_, create_info);
if (!module) {
result.success = false;
result.error_message = "Failed to create shader module";
return result;
}
// 存储
std::string name_str(name);
result.module = module.get();
modules_[name_str] = std::move(module);
module_sources_[name_str] = full_path;
// 注册热重载监视
if (hot_reload_) {
hot_reload_->watch(full_path, name);
// 注册依赖
for (const auto& dep : compile_result.dependencies) {
hot_reload_->add_dependency(name, dep);
}
}
result.success = true;
return result;
}
void shader_library::on_hot_reload_event(const shader_reload_event& event) {
if (!event.success) {
// 编译失败,不更新
return;
}
// 查找受影响的着色器
std::string normalized_path = std::filesystem::weakly_canonical(event.source_path).string();
std::vector<std::string> affected_shaders;
{
std::lock_guard<std::mutex> lock(mutex_);
for (const auto& [name, path] : module_sources_) {
std::string shader_path = std::filesystem::weakly_canonical(path).string();
if (shader_path == normalized_path) {
affected_shaders.push_back(name);
}
}
}
// 重新加载
for (const auto& name : affected_shaders) {
reload(name);
}
}
void shader_library::notify_shader_reload(const std::string& name, shader_module* module) {
std::lock_guard<std::mutex> lock(mutex_);
for (const auto& callback : shader_reload_callbacks_) {
if (callback) {
callback(name, module);
}
}
}
void shader_library::notify_program_reload(const std::string& name, shader_program* program) {
std::lock_guard<std::mutex> lock(mutex_);
for (const auto& callback : program_reload_callbacks_) {
if (callback) {
callback(name, program);
}
}
}
void shader_library::rebuild_dependent_programs(const std::string& shader_name) {
std::vector<std::string> affected_programs;
{
std::lock_guard<std::mutex> lock(mutex_);
for (const auto& [prog_name, info] : program_sources_) {
if (info.vertex_shader == shader_name ||
info.fragment_shader == shader_name ||
info.compute_shader == shader_name) {
affected_programs.push_back(prog_name);
}
}
}
for (const auto& prog_name : affected_programs) {
reload_program(prog_name);
}
}
} // namespace mirai
} // namespace mirai

View File

@@ -3,13 +3,14 @@
* @brief MIRAI 框架着色器库管理
* @author MIRAI Team
* @version 0.1.0
*
*
* @details
* 本文件实现了着色器库管理系统,提供:
* - 着色器模块和程序的集中管理
* - 按名称加载和访问着色器
* - 与热重载系统的集成
* - 从头文件中加载预编译的 SPIR-V 数据
* - 资源生命周期管理
*
* 注意:所有着色器由 tools/shader_compile 编译为 SPIR-V 并生成到头文件中
*/
#pragma once
@@ -17,11 +18,7 @@
#include "shader_types.hpp"
#include "shader_module.hpp"
#include "shader_program.hpp"
#include "shader_compiler.hpp"
#include "shader_cache.hpp"
#include "shader_hot_reload.hpp"
#include <filesystem>
#include <functional>
#include <memory>
#include <mutex>
@@ -35,29 +32,6 @@ typedef struct VkDevice_T* VkDevice;
namespace mirai {
/**
* @brief 着色器库配置
*/
struct shader_library_config {
/// 默认着色器搜索路径
std::vector<std::filesystem::path> search_paths;
/// 是否启用热重载
bool enable_hot_reload{false};
/// 是否启用缓存
bool enable_cache{true};
/// 缓存配置
shader_cache_config cache_config;
/// 热重载配置
hot_reload_config hot_reload_config;
/// 默认编译选项
shader_compile_options default_compile_options;
};
/**
* @brief 着色器加载结果
*/
@@ -86,50 +60,35 @@ struct program_load_result {
shader_program* program{nullptr};
};
/**
* @brief 着色器重载回调类型
*/
using shader_reload_callback = std::function<void(const std::string& name, shader_module* new_module)>;
/**
* @brief 程序重载回调类型
*/
using program_reload_callback = std::function<void(const std::string& name, shader_program* new_program)>;
/**
* @brief 着色器库
*
*
* @details
* 集中管理所有着色器模块和程序,提供加载、访问、重载功能
*
* 集中管理所有着色器模块和程序,从预编译的 SPIR-V 数据加载
* SPIR-V 数据由着色器编译器生成在头文件中。
*
* 使用示例:
* @code
* shader_library_config config;
* config.search_paths = {"shaders/"};
* config.enable_hot_reload = true;
*
* shader_library library(device, config);
*
* // 加载单个着色器
* auto result = library.load("basic_vert", "basic.vert");
* #include "generated/basic_shaders.hpp"
*
* shader_library library(device);
*
* // 从预编译的 SPIR-V 数据加载着色器
* auto result = library.load_spirv_data(
* "basic_vert",
* basic_vert_spirv,
* shader_stage::vertex
* );
* if (!result.success) {
* std::cerr << "Failed: " << result.error_message << std::endl;
* }
*
* // 加载着色器程序
* auto prog_result = library.load_program("basic", "basic.vert", "basic.frag");
*
*
* // 创建着色器程序
* auto prog_result = library.create_program("basic", "basic_vert", "basic_frag");
*
* // 获取着色器
* shader_module* vs = library.get("basic_vert");
* shader_program* prog = library.get_program("basic");
*
* // 注册重载回调
* library.on_shader_reload([](const std::string& name, shader_module* module) {
* std::cout << "Shader reloaded: " << name << std::endl;
* });
*
* // 主循环中更新(用于热重载)
* library.update();
* @endcode
*/
class shader_library {
@@ -140,13 +99,6 @@ public:
*/
explicit shader_library(VkDevice device);
/**
* @brief 带配置的构造函数
* @param device Vulkan 设备
* @param config 库配置
*/
shader_library(VkDevice device, const shader_library_config& config);
/**
* @brief 析构函数
*/
@@ -161,78 +113,33 @@ public:
shader_library& operator=(shader_library&&) = delete;
/**
* @brief 加载着色器
* @brief 从 SPIR-V 数据加载着色器
* @param name 着色器名称
* @param path 文件路径(相对于搜索路径)
* @param options 编译选项(可选)
* @return 加载结果
*/
[[nodiscard]] shader_load_result load(
std::string_view name,
const std::filesystem::path& path,
const shader_compile_options& options = {}
);
/**
* @brief 加载着色器(自动推断阶段)
* @param name 着色器名称
* @param path 文件路径
* @param spirv SPIR-V 字节码
* @param stage 着色器阶段
* @param options 编译选项
* @param entry_point 入口点名称
* @return 加载结果
*/
[[nodiscard]] shader_load_result load(
[[nodiscard]] shader_load_result load_spirv_data(
std::string_view name,
const std::filesystem::path& path,
std::span<const u32> spirv,
shader_stage stage,
const shader_compile_options& options = {}
std::string_view entry_point = "main"
);
/**
* @brief 从源码加载着色器
* @brief 从 shader_binary 加载着色器
* @param name 着色器名称
* @param source 源码
* @param stage 着色器阶段
* @param options 编译选项
* @param binary 着色器二进制数据
* @return 加载结果
*/
[[nodiscard]] shader_load_result load_from_source(
[[nodiscard]] shader_load_result load_binary(
std::string_view name,
std::string_view source,
shader_stage stage,
const shader_compile_options& options = {}
const shader_binary& binary
);
/**
* @brief 加载着色器程序
* @param name 程序名称
* @param vertex_path 顶点着色器路径
* @param fragment_path 片段着色器路径
* @param options 编译选项
* @return 加载结果
*/
[[nodiscard]] program_load_result load_program(
std::string_view name,
const std::filesystem::path& vertex_path,
const std::filesystem::path& fragment_path,
const shader_compile_options& options = {}
);
/**
* @brief 加载计算着色器程序
* @param name 程序名称
* @param compute_path 计算着色器路径
* @param options 编译选项
* @return 加载结果
*/
[[nodiscard]] program_load_result load_compute_program(
std::string_view name,
const std::filesystem::path& compute_path,
const shader_compile_options& options = {}
);
/**
* @brief 创建程序(从已加载的模块)
* @brief 创建图形着色器程序(从已加载的模块)
* @param name 程序名称
* @param vertex_name 顶点着色器名称
* @param fragment_name 片段着色器名称
@@ -244,6 +151,17 @@ public:
std::string_view fragment_name
);
/**
* @brief 创建计算着色器程序(从已加载的模块)
* @param name 程序名称
* @param compute_name 计算着色器名称
* @return 加载结果
*/
[[nodiscard]] program_load_result create_compute_program(
std::string_view name,
std::string_view compute_name
);
/**
* @brief 获取着色器模块
* @param name 着色器名称
@@ -291,31 +209,6 @@ public:
*/
void unload_all();
/**
* @brief 重新加载着色器
* @param name 着色器名称
* @return 加载结果
*/
[[nodiscard]] shader_load_result reload(std::string_view name);
/**
* @brief 重新加载程序
* @param name 程序名称
* @return 加载结果
*/
[[nodiscard]] program_load_result reload_program(std::string_view name);
/**
* @brief 重新加载所有着色器
* @return 重新加载的数量
*/
u32 reload_all();
/**
* @brief 更新(处理热重载等)
*/
void update();
/**
* @brief 获取所有着色器名称
* @return 名称列表
@@ -339,146 +232,19 @@ public:
* @return 数量
*/
[[nodiscard]] u32 get_program_count() const;
/**
* @brief 注册着色器重载回调
* @param callback 回调函数
*/
void on_shader_reload(shader_reload_callback callback);
/**
* @brief 注册程序重载回调
* @param callback 回调函数
*/
void on_program_reload(program_reload_callback callback);
/**
* @brief 获取编译器
* @return 编译器引用
*/
[[nodiscard]] shader_compiler& get_compiler() { return compiler_; }
/**
* @brief 获取缓存
* @return 缓存指针,未启用返回 nullptr
*/
[[nodiscard]] shader_cache* get_cache() { return cache_.get(); }
/**
* @brief 获取热重载管理器
* @return 热重载指针,未启用返回 nullptr
*/
[[nodiscard]] shader_hot_reload* get_hot_reload() { return hot_reload_.get(); }
/**
* @brief 获取配置
* @return 配置引用
*/
[[nodiscard]] const shader_library_config& get_config() const noexcept { return config_; }
/**
* @brief 添加搜索路径
* @param path 搜索路径
*/
void add_search_path(const std::filesystem::path& path);
/**
* @brief 查找文件
* @param filename 文件名
* @return 完整路径,未找到返回空
*/
[[nodiscard]] std::optional<std::filesystem::path> find_file(const std::filesystem::path& filename) const;
/**
* @brief 启用热重载
*/
void enable_hot_reload();
/**
* @brief 禁用热重载
*/
void disable_hot_reload();
/**
* @brief 检查热重载是否启用
* @return 是否启用
*/
[[nodiscard]] bool is_hot_reload_enabled() const noexcept;
private:
/// Vulkan 设备
VkDevice device_;
/// 配置
shader_library_config config_;
/// 编译器
shader_compiler compiler_;
/// 缓存
std::unique_ptr<shader_cache> cache_;
/// 热重载
std::unique_ptr<shader_hot_reload> hot_reload_;
/// 着色器模块映射
std::unordered_map<std::string, shader_module_ptr> modules_;
/// 着色器程序映射
std::unordered_map<std::string, shader_program_ptr> programs_;
/// 着色器源文件映射
std::unordered_map<std::string, std::filesystem::path> module_sources_;
/// 程序组成信息
struct program_info {
std::string vertex_shader;
std::string fragment_shader;
std::string compute_shader;
std::filesystem::path vertex_path;
std::filesystem::path fragment_path;
std::filesystem::path compute_path;
};
std::unordered_map<std::string, program_info> program_sources_;
/// 着色器重载回调
std::vector<shader_reload_callback> shader_reload_callbacks_;
/// 程序重载回调
std::vector<program_reload_callback> program_reload_callbacks_;
/// 互斥锁
mutable std::mutex mutex_;
/**
* @brief 内部加载着色器
*/
[[nodiscard]] shader_load_result load_internal(
std::string_view name,
const std::filesystem::path& full_path,
shader_stage stage,
const shader_compile_options& options
);
/**
* @brief 处理热重载事件
*/
void on_hot_reload_event(const shader_reload_event& event);
/**
* @brief 通知着色器重载
*/
void notify_shader_reload(const std::string& name, shader_module* module);
/**
* @brief 通知程序重载
*/
void notify_program_reload(const std::string& name, shader_program* program);
/**
* @brief 重建使用指定着色器的程序
*/
void rebuild_dependent_programs(const std::string& shader_name);
};
} // namespace mirai
} // namespace mirai

View File

@@ -69,7 +69,6 @@ bool shader_module::initialize(vk::Device device, const shader_module_create_inf
stage_ = create_info.stage;
entry_point_ = create_info.entry_point;
spirv_ = create_info.spirv;
source_path_ = create_info.source_path;
#if MIRAI_DEBUG
if (!create_info.name.empty()) {

View File

@@ -7,6 +7,8 @@
* @details
* 本文件定义了着色器模块类,封装了 Vulkan VkShaderModule
* 并持有 SPIR-V 字节码和反射信息。
*
* SPIR-V 数据由着色器编译器直接生成在头文件中。
*/
#pragma once
@@ -17,7 +19,6 @@
#include "vulkan_types.hpp"
#include <filesystem>
#include <memory>
#include <span>
#include <string>
@@ -42,9 +43,6 @@ struct shader_module_create_info {
/// 模块名称(用于调试)
std::string name;
/// 源文件路径(可选,用于热重载)
std::filesystem::path source_path;
/**
* @brief 从 shader_binary 创建
* @param binary 着色器二进制
@@ -63,23 +61,30 @@ struct shader_module_create_info {
* @brief 着色器模块
*
* @details
* 封装了一个编译的着色器阶段,持有:
* 封装了一个编译的着色器阶段,持有:
* - Vulkan VkShaderModule 句柄
* - SPIR-V 字节码(用于反射和缓存
* - SPIR-V 字节码(用于反射)
* - 反射信息
*
*
* 使用示例:
* @code
* // 从编译结果创建
* shader_compile_result compile_result = compiler.compile_from_file("shader.vert", shader_stage::vertex);
*
* shader_module_create_info info;
* info.stage = shader_stage::vertex;
* info.spirv = compile_result.spirv;
* info.entry_point = "main";
*
* auto module = shader_module::create(device, info);
* // 从编译的 SPIR-V 数据创建(由编译器生成在头文件中)
* #include "generated_shaders/basic_shaders.hpp"
*
* auto module = shader_module::create_from_spirv(
* device,
* basic_vert_spirv,
* shader_stage::vertex
* );
*
* // 或者从 shader_binary 创建
* shader_binary binary;
* binary.stage = shader_stage::vertex;
* binary.spirv = basic_vert_spirv;
* binary.entry_point = "main";
*
* auto module2 = shader_module::create_from_binary(device, binary);
*
* // 使用模块
* VkShaderModule vk_module = module->get_vulkan_module();
* const auto& reflection = module->get_reflection();
@@ -165,20 +170,6 @@ public:
*/
[[nodiscard]] vk::ShaderModule get_vulkan_module() const noexcept { return module_; }
/**
* @brief 获取源文件路径
* @return 源文件路径
*/
[[nodiscard]] const std::filesystem::path& get_source_path() const noexcept {
return source_path_;
}
/**
* @brief 设置源文件路径
* @param path 源文件路径
*/
void set_source_path(const std::filesystem::path& path) { source_path_ = path; }
/**
* @brief 检查模块是否有效
* @return 是否有效
@@ -292,9 +283,6 @@ private:
/// 反射信息
shader_reflection reflection_;
/// 源文件路径
std::filesystem::path source_path_;
};
/**

View File

@@ -15,8 +15,6 @@
#pragma once
#include <filesystem>
#include "types/types.hpp"
#include "shader_types.hpp"
@@ -130,9 +128,10 @@ struct shader_reflection_data {
*
* @example
* @code
* std::vector<u32> spirv = load_spirv_file("shader.spv");
* #include "generated/shader_spv.hpp"
*
* shader_reflection reflection;
* if (reflection.reflect(spirv)) {
* if (reflection.reflect(basic_vert_spirv)) {
* auto& inputs = reflection.get_input_attributes();
* auto& uniforms = reflection.get_uniform_buffers();
* // ...
@@ -450,13 +449,6 @@ private:
// 辅助函数
// ================================================================================================
/**
* @brief 从 SPIR-V 文件加载并创建反射
* @param path 文件路径
* @return 反射对象,如果失败则 is_valid() 返回 false
*/
[[nodiscard]] shader_reflection load_reflection_from_file(const std::filesystem::path& path);
/**
* @brief 合并多个着色器阶段的反射信息
* @param reflections 反射对象列表

View File

@@ -22,6 +22,7 @@
#include <optional>
#include <variant>
#include <array>
#include <span>
namespace mirai {
@@ -1162,4 +1163,322 @@ struct workgroup_size {
}
};
// ================================================================================================
// 静态反射元数据结构(支持 constexpr 初始化)
// ================================================================================================
/**
* @brief 静态 Uniform 成员信息
*
* 用于编译时反射,支持 constexpr 初始化
*/
struct static_uniform_member {
/// 成员名称
const char* name{nullptr};
/// 类型名称(如 "float", "vec4", "mat4"
const char* type{nullptr};
/// 在 Uniform Buffer 中的偏移量(字节)
u32 offset{0};
/// 成员大小(字节)
u32 size{0};
/**
* @brief 默认构造函数
*/
constexpr static_uniform_member() = default;
/**
* @brief 构造函数
* @param n 成员名称
* @param t 类型名称
* @param off 偏移量
* @param sz 大小
*/
constexpr static_uniform_member(const char* n, const char* t, u32 off, u32 sz)
: name(n), type(t), offset(off), size(sz) {}
};
/**
* @brief 静态 Uniform Block 信息
*
* 用于编译时反射,支持 constexpr 初始化
*/
struct static_uniform_block_info {
/// Block 名称
const char* name{nullptr};
/// 描述符集索引
u32 set{0};
/// 绑定点索引
u32 binding{0};
/// Block 总大小(字节)
u32 size{0};
/// 成员列表
std::span<const static_uniform_member> members{};
/**
* @brief 默认构造函数
*/
constexpr static_uniform_block_info() = default;
/**
* @brief 构造函数
* @param n Block 名称
* @param s 描述符集索引
* @param b 绑定点索引
* @param sz 大小
* @param m 成员列表
*/
constexpr static_uniform_block_info(const char* n, u32 s, u32 b, u32 sz,
std::span<const static_uniform_member> m = {})
: name(n), set(s), binding(b), size(sz), members(m) {}
/**
* @brief 查找成员
* @param member_name 成员名称
* @return 成员信息的指针,如果未找到返回 nullptr
*/
[[nodiscard]] constexpr const static_uniform_member* find_member(std::string_view member_name) const noexcept {
for (const auto& member : members) {
if (member.name && member_name == member.name) {
return &member;
}
}
return nullptr;
}
};
/**
* @brief 静态纹理/采样器信息
*
* 用于编译时反射,支持 constexpr 初始化
*/
struct static_texture_info {
/// 纹理名称
const char* name{nullptr};
/// 描述符集索引
u32 set{0};
/// 绑定点索引
u32 binding{0};
/// 纹理类型(如 "texture2D", "textureCube", "texture2DArray"
const char* type{nullptr};
/**
* @brief 默认构造函数
*/
constexpr static_texture_info() = default;
/**
* @brief 构造函数
* @param n 纹理名称
* @param s 描述符集索引
* @param b 绑定点索引
* @param t 纹理类型
*/
constexpr static_texture_info(const char* n, u32 s, u32 b, const char* t)
: name(n), set(s), binding(b), type(t) {}
};
/**
* @brief 静态 Push Constant 成员信息
*
* 用于编译时反射,支持 constexpr 初始化
*/
struct static_push_constant_member {
/// 成员名称
const char* name{nullptr};
/// 类型名称
const char* type{nullptr};
/// 偏移量(字节)
u32 offset{0};
/// 大小(字节)
u32 size{0};
/**
* @brief 默认构造函数
*/
constexpr static_push_constant_member() = default;
/**
* @brief 构造函数
*/
constexpr static_push_constant_member(const char* n, const char* t, u32 off, u32 sz)
: name(n), type(t), offset(off), size(sz) {}
};
/**
* @brief 静态 Push Constant 信息
*
* 用于编译时反射,支持 constexpr 初始化
*/
struct static_push_constant_info {
/// Block 名称
const char* name{nullptr};
/// 着色器阶段标志位
u32 stage_flags{0};
/// 偏移量(字节)
u32 offset{0};
/// 总大小(字节)
u32 size{0};
/// 成员列表
std::span<const static_push_constant_member> members{};
/**
* @brief 默认构造函数
*/
constexpr static_push_constant_info() = default;
/**
* @brief 构造函数
*/
constexpr static_push_constant_info(const char* n, u32 flags, u32 off, u32 sz,
std::span<const static_push_constant_member> m = {})
: name(n), stage_flags(flags), offset(off), size(sz), members(m) {}
};
/**
* @brief 静态顶点输入属性信息
*
* 用于编译时反射,支持 constexpr 初始化
*/
struct static_vertex_input_info {
/// 属性名称
const char* name{nullptr};
/// 位置索引location
u32 location{0};
/// 类型名称
const char* type{nullptr};
/// Vulkan 格式
u32 format{0};
/// 偏移量(字节)
u32 offset{0};
/**
* @brief 默认构造函数
*/
constexpr static_vertex_input_info() = default;
/**
* @brief 构造函数
*/
constexpr static_vertex_input_info(const char* n, u32 loc, const char* t, u32 fmt, u32 off)
: name(n), location(loc), type(t), format(fmt), offset(off) {}
};
/**
* @brief 静态着色器反射信息
*
* 聚合所有着色器反射数据,支持 constexpr 初始化
*/
struct static_shader_reflection {
/// 着色器名称
const char* name{nullptr};
/// Uniform Blocks 列表
std::span<const static_uniform_block_info> uniform_blocks{};
/// 纹理/采样器列表
std::span<const static_texture_info> textures{};
/// Push Constants 列表
std::span<const static_push_constant_info> push_constants{};
/// 顶点输入属性列表
std::span<const static_vertex_input_info> vertex_inputs{};
/**
* @brief 默认构造函数
*/
constexpr static_shader_reflection() = default;
/**
* @brief 构造函数
*/
constexpr static_shader_reflection(
const char* n,
std::span<const static_uniform_block_info> ub = {},
std::span<const static_texture_info> tex = {},
std::span<const static_push_constant_info> pc = {},
std::span<const static_vertex_input_info> vi = {})
: name(n), uniform_blocks(ub), textures(tex), push_constants(pc), vertex_inputs(vi) {}
/**
* @brief 查找 Uniform Block
* @param block_name Block 名称
* @return Block 信息的指针,如果未找到返回 nullptr
*/
[[nodiscard]] constexpr const static_uniform_block_info* find_uniform_block(std::string_view block_name) const noexcept {
for (const auto& block : uniform_blocks) {
if (block.name && block_name == block.name) {
return &block;
}
}
return nullptr;
}
/**
* @brief 查找纹理
* @param texture_name 纹理名称
* @return 纹理信息的指针,如果未找到返回 nullptr
*/
[[nodiscard]] constexpr const static_texture_info* find_texture(std::string_view texture_name) const noexcept {
for (const auto& tex : textures) {
if (tex.name && texture_name == tex.name) {
return &tex;
}
}
return nullptr;
}
/**
* @brief 按 set 和 binding 查找 Uniform Block
* @param set 描述符集索引
* @param binding 绑定点索引
* @return Block 信息的指针,如果未找到返回 nullptr
*/
[[nodiscard]] constexpr const static_uniform_block_info* find_uniform_block_by_binding(u32 set, u32 binding) const noexcept {
for (const auto& block : uniform_blocks) {
if (block.set == set && block.binding == binding) {
return &block;
}
}
return nullptr;
}
/**
* @brief 按 set 和 binding 查找纹理
* @param set 描述符集索引
* @param binding 绑定点索引
* @return 纹理信息的指针,如果未找到返回 nullptr
*/
[[nodiscard]] constexpr const static_texture_info* find_texture_by_binding(u32 set, u32 binding) const noexcept {
for (const auto& tex : textures) {
if (tex.set == set && tex.binding == binding) {
return &tex;
}
}
return nullptr;
}
};
} // namespace mirai

View File

@@ -0,0 +1,60 @@
#version 450
// ============================================================
// 高斯模糊效果示例 (Backdrop 模式)
// 使用用户 UBO (Set 1) 传递模糊参数
// ============================================================
// 框架输入
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
// 背景纹理(框架提供)
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
// Set 1: 用户 UBO - 模糊参数
layout(set = 1, binding = 0, std140) uniform BlurParams {
float sigma; // 高斯分布标准差
int samples; // 采样半径(实际采样数为 (2*samples+1)^2
vec2 texel_size; // 纹理像素大小 (1.0/width, 1.0/height)
} params;
void main() {
vec4 color = vec4(0.0);
float total_weight = 0.0;
// 高斯模糊核
float sigma2 = params.sigma * params.sigma;
float inv_2sigma2 = 1.0 / (2.0 * sigma2);
// 双重循环采样
for (int i = -params.samples; i <= params.samples; i++) {
for (int j = -params.samples; j <= params.samples; j++) {
// 计算偏移
vec2 offset = vec2(float(i), float(j)) * params.texel_size;
// 计算高斯权重
float dist2 = float(i * i + j * j);
float weight = exp(-dist2 * inv_2sigma2);
// 累加采样
color += texture(u_backdrop, v_uv + offset) * weight;
total_weight += weight;
}
}
// 归一化
frag_color = color / total_weight;
// 应用框架透明度
frag_color.a *= widget.u_opacity;
}

View File

@@ -0,0 +1,42 @@
#version 450
// ============================================================
// 线性渐变效果示例 (Procedural 模式)
// 使用 Push Constant 传递颜色和角度
// ============================================================
// 框架输入
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
// Push Constant: 渐变参数
layout(push_constant) uniform PushConstants {
vec4 color_start; // 起始颜色
vec4 color_end; // 结束颜色
float angle; // 渐变角度(弧度)
float _pad[3];
} pc;
void main() {
// 计算渐变方向向量
vec2 dir = vec2(cos(pc.angle), sin(pc.angle));
// 计算当前位置在渐变方向上的投影
// v_local_pos 范围是 0-1将其转换到 -0.5 到 0.5 范围
float t = dot(v_local_pos - 0.5, dir) + 0.5;
// 线性插值颜色
frag_color = mix(pc.color_start, pc.color_end, clamp(t, 0.0, 1.0));
// 应用框架透明度
frag_color.a *= widget.u_opacity;
}

View File

@@ -0,0 +1,39 @@
#version 450
// ============================================================
// 粒子系统片段着色器 (Custom Mesh 模式)
// 绘制圆形粒子,带软边缘
// ============================================================
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec4 v_color;
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
void main() {
// 计算到中心的距离
vec2 center = v_uv - 0.5;
float dist = length(center) * 2.0;
// 圆形遮罩,带软边缘
float alpha = 1.0 - smoothstep(0.7, 1.0, dist);
// 添加发光效果
float glow = exp(-dist * dist * 2.0);
frag_color = v_color;
frag_color.rgb += glow * 0.3; // 添加一点发光
frag_color.a *= alpha * widget.u_opacity;
// 丢弃完全透明的片段
if (frag_color.a < 0.01) {
discard;
}
}

View File

@@ -0,0 +1,73 @@
#version 450
// ============================================================
// 粒子系统顶点着色器 (Custom Mesh 模式)
// 使用实例化渲染绘制粒子
// ============================================================
// 四边形顶点Billboard- 使用 gl_VertexIndex 生成
const vec2 QUAD_VERTICES[6] = vec2[](
vec2(-0.5, -0.5), vec2(0.5, -0.5), vec2(0.5, 0.5),
vec2(-0.5, -0.5), vec2(0.5, 0.5), vec2(-0.5, 0.5)
);
const vec2 QUAD_UVS[6] = vec2[](
vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(1.0, 1.0),
vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 1.0)
);
// 实例数据输入
layout(location = 0) in vec3 a_position; // 粒子位置
layout(location = 1) in float a_size; // 粒子大小
layout(location = 2) in vec4 a_color; // 粒子颜色
layout(location = 3) in float a_rotation; // 粒子旋转
// 输出到片段着色器
layout(location = 0) out vec2 v_uv;
layout(location = 1) out vec4 v_color;
// Set 0: 框架专用
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds; // (x, y, width, height)
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
// Push Constant: 变换参数
layout(push_constant) uniform PushConstants {
mat4 view_proj;
vec2 viewport_size;
vec2 _pad;
} pc;
void main() {
// 获取四边形顶点
int vertex_id = gl_VertexIndex % 6;
vec2 quad_pos = QUAD_VERTICES[vertex_id];
v_uv = QUAD_UVS[vertex_id];
// 应用旋转
float c = cos(a_rotation);
float s = sin(a_rotation);
mat2 rot = mat2(c, -s, s, c);
quad_pos = rot * quad_pos;
// 应用大小(转换为归一化坐标)
vec2 size_normalized = a_size / pc.viewport_size;
quad_pos *= size_normalized;
// 计算最终位置
// a_position 是归一化坐标 (0-1)
vec2 pos = a_position.xy + quad_pos;
// 应用 widget bounds
pos = pos * widget.u_bounds.zw + widget.u_bounds.xy;
// 转换到 NDC (-1 到 1)
vec2 ndc = pos * 2.0 - 1.0;
// Vulkan Y轴翻转
gl_Position = vec4(ndc.x, -ndc.y, a_position.z, 1.0);
v_color = a_color;
}

View File

@@ -0,0 +1,53 @@
#version 450
// ============================================================
// 圆角矩形遮罩示例 (Mask 模式)
// 使用 Push Constant 传递圆角半径
// ============================================================
// 框架输入
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec2 v_local_pos;
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds;
float u_opacity;
float _pad0, _pad1, _pad2;
} widget;
// Push Constant: 遮罩参数
layout(push_constant) uniform PushConstants {
vec4 color; // 遮罩颜色
float radius; // 圆角半径(像素)
float _pad[3];
} pc;
// 计算圆角矩形的有符号距离场 (SDF)
float rounded_rect_sdf(vec2 p, vec2 half_size, float radius) {
vec2 q = abs(p) - half_size + radius;
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
}
void main() {
// 获取 widget 尺寸
vec2 size = widget.u_bounds.zw;
vec2 half_size = size * 0.5;
// 将局部坐标转换到以中心为原点的坐标系
// v_local_pos 范围是 0-1转换到 -half_size 到 half_size
vec2 p = (v_local_pos - 0.5) * size;
// 计算 SDF
float d = rounded_rect_sdf(p, half_size, pc.radius);
// 使用 smoothstep 进行抗锯齿
// d < 0 表示在形状内部d > 0 表示在形状外部
float alpha = 1.0 - smoothstep(-1.0, 1.0, d);
// 输出遮罩颜色
frag_color = pc.color;
frag_color.a *= alpha * widget.u_opacity;
}

View File

@@ -0,0 +1,41 @@
#version 450
// ============================================================
// 框架内置全屏顶点着色器
// 用于全屏后处理效果
// ============================================================
// 无顶点输入 - 使用 gl_VertexIndex 生成全屏三角形
// 输出到片段着色器
layout(location = 0) out vec2 v_uv;
layout(location = 1) out vec2 v_local_pos;
// 框架 UBO - Set 0 专用
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds; // (x, y, width, height) - 归一化屏幕坐标
float u_opacity; // 0.0 - 1.0
float _pad0;
float _pad1;
float _pad2;
} widget;
void main() {
// 生成覆盖整个屏幕的三角形
// 使用一个大三角形覆盖整个视口,比两个三角形更高效
vec2 positions[3] = vec2[3](
vec2(-1.0, -1.0),
vec2( 3.0, -1.0),
vec2(-1.0, 3.0)
);
vec2 uvs[3] = vec2[3](
vec2(0.0, 0.0),
vec2(2.0, 0.0),
vec2(0.0, 2.0)
);
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
v_uv = uvs[gl_VertexIndex];
v_local_pos = uvs[gl_VertexIndex];
}

View File

@@ -0,0 +1,38 @@
#version 450
// ============================================================
// 框架内置四边形顶点着色器
// 用于 Backdrop/Procedural/Mask 模式
// ============================================================
// 顶点输入
layout(location = 0) in vec2 a_position; // 顶点位置 (0-1 范围)
layout(location = 1) in vec2 a_uv; // UV 坐标
// 输出到片段着色器
layout(location = 0) out vec2 v_uv;
layout(location = 1) out vec2 v_local_pos;
// 框架 UBO - Set 0 专用
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds; // (x, y, width, height) - 归一化屏幕坐标
float u_opacity; // 0.0 - 1.0
float _pad0;
float _pad1;
float _pad2;
} widget;
void main() {
// 将顶点位置转换到 NDC
// a_position 是 0-1 范围widget.u_bounds.xy 是左上角位置zw 是宽高
vec2 pos = a_position * widget.u_bounds.zw + widget.u_bounds.xy;
// 转换到 NDC (-1 到 1)
vec2 ndc = pos * 2.0 - 1.0;
// Vulkan Y轴翻转
gl_Position = vec4(ndc.x, -ndc.y, 0.0, 1.0);
v_uv = a_uv;
v_local_pos = a_position;
}

View File

@@ -0,0 +1,62 @@
#version 450
// ============================================================
// Backdrop 模式片段着色器模板
// 用于后处理效果,框架提供背景纹理
// ============================================================
// ============================================================
// 框架输入(不要修改)
// ============================================================
layout(location = 0) in vec2 v_uv; // UV 坐标 (0-1)
layout(location = 1) in vec2 v_local_pos; // 局部坐标 (0-1),相对于 widget bounds
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用(不要修改)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds; // (x, y, width, height)
float u_opacity; // 0.0 - 1.0
float _pad0;
float _pad1;
float _pad2;
} widget;
// 背景纹理(框架提供)
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
// ============================================================
// 用户区域(自由定义)
// ============================================================
// Set 1: 用户 UBO可选
// layout(set = 1, binding = 0, std140) uniform MyParams {
// float radius;
// int samples;
// vec2 texel_size;
// // ... 自定义参数
// } params;
// Set 2: 用户纹理(可选)
// layout(set = 2, binding = 0) uniform sampler2D u_my_texture;
// Push Constant可选最大 128 bytes
// layout(push_constant) uniform PushConstants {
// // ... 自定义参数
// } pc;
// ============================================================
// 主函数
// ============================================================
void main() {
// 采样背景纹理
vec4 color = texture(u_backdrop, v_uv);
// TODO: 用户实现后处理效果
// 例如:模糊、色彩调整、扭曲等
frag_color = color;
// 应用框架提供的透明度
frag_color.a *= widget.u_opacity;
}

View File

@@ -0,0 +1,62 @@
#version 450
// ============================================================
// Mask 模式片段着色器模板
// 用于遮罩效果,输出单通道 alpha 值
// ============================================================
// ============================================================
// 框架输入(不要修改)
// ============================================================
layout(location = 0) in vec2 v_uv; // UV 坐标 (0-1)
layout(location = 1) in vec2 v_local_pos; // 局部坐标 (0-1),相对于 widget bounds
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用(不要修改)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds; // (x, y, width, height)
float u_opacity; // 0.0 - 1.0
float _pad0;
float _pad1;
float _pad2;
} widget;
// ============================================================
// 用户区域(自由定义)
// ============================================================
// Set 1: 用户 UBO可选
// layout(set = 1, binding = 0, std140) uniform MyParams {
// // ... 自定义参数
// } params;
// Set 2: 用户纹理(可选)
// layout(set = 2, binding = 0) uniform sampler2D u_mask_texture;
// Push Constant可选最大 128 bytes
layout(push_constant) uniform PushConstants {
vec4 color; // 遮罩颜色
float corner_radius; // 圆角半径
float _pad[3];
} pc;
// ============================================================
// 主函数
// ============================================================
void main() {
// TODO: 用户实现遮罩计算
// 示例:圆角矩形遮罩
vec2 size = widget.u_bounds.zw;
vec2 half_size = size * 0.5;
vec2 p = (v_local_pos - 0.5) * size;
vec2 q = abs(p) - half_size + pc.corner_radius;
float d = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - pc.corner_radius;
float mask = 1.0 - smoothstep(-1.0, 1.0, d);
// 输出遮罩颜色alpha 通道包含遮罩值
frag_color = pc.color;
frag_color.a *= mask * widget.u_opacity;
}

View File

@@ -0,0 +1,54 @@
#version 450
// ============================================================
// Custom Mesh 模式顶点着色器模板
// 用于自定义网格渲染,用户需要同时提供顶点和片段着色器
// ============================================================
// ============================================================
// 用户定义顶点输入
// ============================================================
layout(location = 0) in vec3 a_position; // 顶点位置
layout(location = 1) in vec2 a_uv; // UV 坐标
layout(location = 2) in vec4 a_color; // 顶点颜色
// 输出到片段着色器
layout(location = 0) out vec2 v_uv;
layout(location = 1) out vec4 v_color;
// Set 0: 框架专用(不要修改)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds; // (x, y, width, height)
float u_opacity; // 0.0 - 1.0
float _pad0;
float _pad1;
float _pad2;
} widget;
// ============================================================
// 用户区域(自由定义)
// ============================================================
// Set 1: 用户 UBO可选
// layout(set = 1, binding = 0, std140) uniform MyParams {
// mat4 view;
// mat4 projection;
// // ... 自定义参数
// } params;
// Push Constant可选最大 128 bytes
layout(push_constant) uniform PushConstants {
mat4 mvp; // Model-View-Projection 矩阵
} pc;
// ============================================================
// 主函数
// ============================================================
void main() {
// 使用 MVP 矩阵变换顶点位置
gl_Position = pc.mvp * vec4(a_position, 1.0);
// 传递数据到片段着色器
v_uv = a_uv;
v_color = a_color;
}

View File

@@ -0,0 +1,55 @@
#version 450
// ============================================================
// Procedural 模式片段着色器模板
// 用于程序化生成效果
// ============================================================
// ============================================================
// 框架输入(不要修改)
// ============================================================
layout(location = 0) in vec2 v_uv; // UV 坐标 (0-1)
layout(location = 1) in vec2 v_local_pos; // 局部坐标 (0-1),相对于 widget bounds
layout(location = 0) out vec4 frag_color;
// Set 0: 框架专用(不要修改)
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
vec4 u_bounds; // (x, y, width, height)
float u_opacity; // 0.0 - 1.0
float _pad0;
float _pad1;
float _pad2;
} widget;
// ============================================================
// 用户区域(自由定义)
// ============================================================
// Set 1: 用户 UBO可选
// layout(set = 1, binding = 0, std140) uniform MyParams {
// float time;
// // ... 自定义参数
// } params;
// Set 2: 用户纹理(可选)
// layout(set = 2, binding = 0) uniform sampler2D u_my_texture;
// Push Constant可选最大 128 bytes
layout(push_constant) uniform PushConstants {
vec4 color;
// ... 自定义参数
} pc;
// ============================================================
// 主函数
// ============================================================
void main() {
// TODO: 用户实现程序化效果
// 示例:使用 Push Constant 中的颜色
frag_color = pc.color;
// 应用框架提供的透明度
frag_color.a *= widget.u_opacity;
}

342
src/ui/backdrop_widget.hpp Normal file
View File

@@ -0,0 +1,342 @@
/**
* @file backdrop_widget.hpp
* @brief MIRAI 框架后效处理控件
* @author MIRAI Team
* @version 0.1.0
*
* 本文件定义了后效处理控件backdrop widget
* 对控件所在区域进行后效处理,如模糊、颜色调整等。
* 不能拥有子控件。
*
* 使用方法:
* 1. 编写 GLSL/HLSL 片段着色器,接收 backdrop 纹理作为输入
* 2. 使用着色器编译工具生成绑定
* 3. 继承 backdrop_widget 并设置着色器绑定
*
* @code
* // 着色器示例 (backdrop_blur.frag)
* #version 450
* layout(set = 0, binding = 0) uniform sampler2D u_backdrop;
* layout(location = 0) in vec2 v_uv;
* layout(location = 0) out vec4 fragColor;
*
* void main() {
* vec4 color = texture(sampler2D(u_backdrop, u_sampler), v_uv);
* fragColor = color;
* }
* @endcode
*/
#pragma once
#include "shader_widget.hpp"
namespace mirai {
// ================================================================================================
// 后效控件基类
// ================================================================================================
/**
* @brief 后效控件基类
*
* 对控件所在区域应用后效处理的控件。
* 使用自定义片段着色器对底层图像进行处理。
* 不能拥有子控件。
*
* 渲染流程:
* 1. 渲染完所有子控件后,捕获当前帧缓冲内容到临时纹理
* 2. 使用后效着色器渲染到目标
*
* @tparam T 着色器绑定类型
*/
template<typename T>
class backdrop_widget : public shader_widget<T> {
public:
using base_type = shader_widget<T>;
using bindings_type = T;
MIRAI_OBJECT_TYPE_INFO(backdrop_widget, shader_widget)
/**
* @brief 默认构造函数
*/
backdrop_widget() = default;
/**
* @brief 析构函数
*/
~backdrop_widget() override {
destroy_backdrop_resources();
}
// ============================================================================================
// 渲染参数
// ============================================================================================
/**
* @brief 设置模糊半径
* @param radius 模糊半径
*/
void set_blur_radius(f32 radius) {
blur_radius_ = radius;
this->invalidate_pipeline();
}
/**
* @brief 获取模糊半径
* @return 模糊半径
*/
[[nodiscard]] f32 get_blur_radius() const noexcept { return blur_radius_; }
/**
* @brief 设置强度
* @param intensity 强度
*/
void set_intensity(f32 intensity) {
intensity_ = intensity;
}
/**
* @brief 获取强度
* @return 强度
*/
[[nodiscard]] f32 get_intensity() const noexcept { return intensity_; }
protected:
// ============================================================================================
// 生命周期
// ============================================================================================
void on_mount() override {
base_type::on_mount();
create_backdrop_resources();
}
void on_unmount() override {
destroy_backdrop_resources();
base_type::on_unmount();
}
// ============================================================================================
// 测量和布局
// ============================================================================================
vec2 on_measure(const size_constraints& constraints) override {
// 后效控件通常需要明确的尺寸
auto size = base_type::on_measure(constraints);
// 如果没有明确尺寸,使用约束或默认
if (!this->has_explicit_width()) {
size.x = constraints.preferred_width.value_or(size.x);
size.x = std::min(size.x, constraints.max_width.value_or(f32_MAX));
}
if (!this->has_explicit_height()) {
size.y = constraints.preferred_height.value_or(size.y);
size.y = std::min(size.y, constraints.max_height.value_or(f32_MAX));
}
return size;
}
// ============================================================================================
// 绘制
// ============================================================================================
void on_shader_paint(paint_context& ctx) override {
auto* rc = ctx.get_render_context();
if (!rc) return;
auto& renderer = rc->get_renderer_ref();
auto device = renderer.get_device();
// 绑定管线
if (this->pipeline_) {
rc->bind_pipeline(this->pipeline_);
}
// 设置视口
auto bounds = this->get_bounds();
rc->set_viewport(0.0f, 0.0f, bounds.width, bounds.height);
rc->set_scissor(0, 0, static_cast<u32>(bounds.width), static_cast<u32>(bounds.height));
// 绑定描述符集(使用 backdrop 纹理)
if (this->descriptor_set_) {
// 更新描述符集图像信息
vk::DescriptorImageInfo image_info{};
image_info.imageView = backdrop_image_view_;
image_info.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
image_info.sampler = backdrop_sampler_;
vk::WriteDescriptorSet write{};
write.dstSet = this->descriptor_set_;
write.dstBinding = 0;
write.descriptorCount = 1;
write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
write.pImageInfo = &image_info;
device->get_device().updateDescriptorSets(1, &write, 0, nullptr);
rc->bind_descriptor_sets(vk::PipelineBindPoint::eGraphics,
this->pipeline_layout_,
0,
{this->descriptor_set_});
}
// 设置推送常量
if (this->pipeline_layout_) {
set_push_constants(*rc);
}
// 绘制
rc->draw_fullscreen_quad();
}
// ============================================================================================
// 资源管理
// ============================================================================================
/**
* @brief 创建后效资源
*/
void create_backdrop_resources() {
auto* rc = this->get_render_context();
if (!rc) return;
auto& renderer = rc->get_renderer_ref();
auto device = renderer.get_device();
auto allocator = renderer.get_gpu_allocator();
auto extent = renderer.get_swapchain_extent();
// 创建临时图像(用于捕获 backdrop
VkImageCreateInfo image_info{};
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.extent.width = extent.width;
image_info.extent.height = extent.height;
image_info.extent.depth = 1;
image_info.mipLevels = 1;
image_info.arrayLayers = 1;
image_info.format = renderer.get_swapchain_format();
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
VmaAllocationCreateInfo alloc_info{};
alloc_info.usage = VMA_MEMORY_USAGE_GPU_ONLY;
VkImage image;
auto result = vmaCreateImage(allocator->get_vma_allocator(), &image_info, &alloc_info,
&image, &backdrop_allocation_, nullptr);
if (result != VK_SUCCESS) return;
backdrop_image_ = image;
// 创建图像视图
vk::ImageViewCreateInfo view_info{};
view_info.image = backdrop_image_;
view_info.viewType = vk::ImageViewType::e2D;
view_info.format = renderer.get_swapchain_format();
view_info.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
view_info.subresourceRange.baseMipLevel = 0;
view_info.subresourceRange.levelCount = 1;
view_info.subresourceRange.baseArrayLayer = 0;
view_info.subresourceRange.layerCount = 1;
auto [view_result, view] = device->get_device().createImageView(view_info);
if (view_result != vk::Result::eSuccess) return;
backdrop_image_view_ = view;
// 创建采样器
vk::SamplerCreateInfo sampler_info{};
sampler_info.magFilter = vk::Filter::eLinear;
sampler_info.minFilter = vk::Filter::eLinear;
sampler_info.addressModeU = vk::SamplerAddressMode::eClampToEdge;
sampler_info.addressModeV = vk::SamplerAddressMode::eClampToEdge;
sampler_info.addressModeW = vk::SamplerAddressMode::eClampToEdge;
sampler_info.borderColor = vk::BorderColor::eFloatTransparentBlack;
sampler_info.unnormalizedCoordinates = VK_FALSE;
auto [sampler_result, sampler] = device->get_device().createSampler(sampler_info);
if (sampler_result != vk::Result::eSuccess) return;
backdrop_sampler_ = sampler;
}
/**
* @brief 销毁后效资源
*/
void destroy_backdrop_resources() {
auto* rc = this->get_render_context();
if (!rc) return;
auto& renderer = rc->get_renderer_ref();
auto device = renderer.get_device();
auto allocator = renderer.get_gpu_allocator();
if (backdrop_sampler_) {
device->get_device().destroySampler(backdrop_sampler_);
backdrop_sampler_ = VK_NULL_HANDLE;
}
if (backdrop_image_view_) {
device->get_device().destroyImageView(backdrop_image_view_);
backdrop_image_view_ = VK_NULL_HANDLE;
}
if (backdrop_image_ && backdrop_allocation_) {
vmaDestroyImage(allocator->get_vma_allocator(), backdrop_image_, backdrop_allocation_);
backdrop_image_ = VK_NULL_HANDLE;
backdrop_allocation_ = nullptr;
}
}
/**
* @brief 设置推送常量
*/
virtual void set_push_constants(render_context& ctx) {
// 子类重写以设置特定参数
(void)ctx;
}
// ============================================================================================
// 成员变量
// ============================================================================================
/// 模糊半径
f32 blur_radius_ = 5.0f;
/// 强度
f32 intensity_ = 1.0f;
/// 后效图像
vk::Image backdrop_image_ = VK_NULL_HANDLE;
/// 后效图像分配
VmaAllocation backdrop_allocation_ = nullptr;
/// 后效图像视图
vk::ImageView backdrop_image_view_ = VK_NULL_HANDLE;
/// 后效采样器
vk::Sampler backdrop_sampler_ = VK_NULL_HANDLE;
};
// ================================================================================================
// 便捷类型定义
// ================================================================================================
/**
* @brief 创建后效控件
*
* @tparam T 着色器绑定类型
* @return 后效控件指针
*/
template<typename T>
[[nodiscard]] std::shared_ptr<backdrop_widget<T>> make_backdrop_widget() {
return std::make_shared<backdrop_widget<T>>();
}
} // namespace mirai

291
src/ui/mask_widget.hpp Normal file
View File

@@ -0,0 +1,291 @@
/**
* @file mask_widget.hpp
* @brief MIRAI 框架遮罩渲染控件
* @author MIRAI Team
* @version 0.1.0
*
* 本文件定义了遮罩渲染控件mask widget
* 使用自定义片段着色器对子控件进行遮罩处理。
* 可以拥有子控件。
*
* 使用方法:
* 1. 编写 Slang 片段着色器,使用遮罩纹理和 UV 计算透明度
* 2. 使用着色器编译工具生成绑定
* 3. 继承 mask_widget 并设置着色器绑定
*
* @code
* 改成slang着色器
* @endcode
*/
#pragma once
#include "shader_widget.hpp"
#include "container.hpp"
namespace mirai {
// ================================================================================================
// 遮罩渲染控件
// ================================================================================================
/**
* @brief 遮罩渲染控件
*
* 使用自定义片段着色器对子控件进行遮罩处理的控件。
* 可以拥有子控件。
*
* 渲染流程:
* 1. 首先将子控件渲染到临时帧缓冲/纹理
* 2. 使用遮罩着色器将子控件内容与遮罩纹理合并
*
* @tparam T 着色器绑定类型
*/
template<typename T>
class mask_widget : public container {
public:
using base_type = container;
using bindings_type = T;
MIRAI_OBJECT_TYPE_INFO(mask_widget, container)
/**
* @brief 默认构造函数
*/
mask_widget() = default;
/**
* @brief 析构函数
*/
~mask_widget() override {
destroy_mask_resources();
}
// ============================================================================================
// 遮罩参数
// ============================================================================================
/**
* @brief 设置遮罩纹理
* @param texture 遮罩纹理
*/
void set_mask_texture(std::shared_ptr<texture> texture) {
mask_texture_ = std::move(texture);
invalidate_pipeline();
}
/**
* @brief 获取遮罩纹理
* @return 遮罩纹理
*/
[[nodiscard]] std::shared_ptr<texture> get_mask_texture() const noexcept { return mask_texture_; }
/**
* @brief 设置遮罩反相
* @param invert 是否反相
*/
void set_invert_mask(bool invert) {
invert_mask_ = invert;
invalidate_pipeline();
}
/**
* @brief 获取遮罩反相
* @return 是否反相
*/
[[nodiscard]] bool get_invert_mask() const noexcept { return invert_mask_; }
protected:
// ============================================================================================
// 生命周期
// ============================================================================================
void on_mount() override {
base_type::on_mount();
create_mask_resources();
}
void on_unmount() override {
destroy_mask_resources();
base_type::on_unmount();
}
// ============================================================================================
// 测量和布局
// ============================================================================================
vec2 on_measure(const size_constraints& constraints) override {
// 首先测量子控件
auto content_size = measure_children(constraints);
// 使用子控件大小作为自己的大小
return content_size;
}
void on_layout(const rect2d& bounds) override {
base_type::on_layout(bounds);
}
// ============================================================================================
// 绘制
// ============================================================================================
void on_paint(paint_context& ctx) override {
if (!is_visible()) return;
// 绘制子控件(将渲染到临时纹理)
paint_children(ctx);
// 使用遮罩着色器渲染
paint_with_mask(ctx);
}
/**
* @brief 使用遮罩绘制
* @param ctx 绘制上下文
*/
virtual void paint_with_mask(paint_context& ctx) {
auto* rc = ctx.get_render_context();
if (!rc) return;
// 绑定管线
if (mask_pipeline_) {
rc->bind_pipeline(mask_pipeline_);
}
// 设置视口
auto bounds = get_bounds();
rc->set_viewport(0.0f, 0.0f, bounds.width, bounds.height);
rc->set_scissor(0, 0, static_cast<u32>(bounds.width), static_cast<u32>(bounds.height));
// 绑定描述符集
if (mask_descriptor_set_) {
rc->bind_descriptor_sets(vk::PipelineBindPoint::eGraphics,
mask_pipeline_layout_,
0,
{mask_descriptor_set_});
}
// 设置推送常量
if (mask_pipeline_layout_) {
set_push_constants(*rc);
}
// 绘制
rc->draw_fullscreen_quad();
}
// ============================================================================================
// 资源管理
// ============================================================================================
/**
* @brief 创建遮罩资源
*/
void create_mask_resources() {
// 子类重写以创建特定资源
}
/**
* @brief 销毁遮罩资源
*/
void destroy_mask_resources() {
auto* rc = get_render_context();
if (!rc) return;
auto& renderer = rc->get_renderer_ref();
auto device = renderer.get_device();
if (mask_pipeline_) {
device->get_device().destroyPipeline(mask_pipeline_);
mask_pipeline_ = VK_NULL_HANDLE;
}
if (mask_pipeline_layout_) {
device->get_device().destroyPipelineLayout(mask_pipeline_layout_);
mask_pipeline_layout_ = VK_NULL_HANDLE;
}
if (mask_descriptor_pool_) {
device->get_device().destroyDescriptorPool(mask_descriptor_pool_);
mask_descriptor_pool_ = VK_NULL_HANDLE;
}
}
/**
* @brief 获取渲染上下文
* @return 渲染上下文指针
*/
[[nodiscard]] render_context* get_render_context() const {
// 从外部获取渲染上下文
// 子类或外部代码需要设置
return render_context_.get();
}
/**
* @brief 设置渲染上下文
* @param ctx 渲染上下文
*/
void set_render_context(std::unique_ptr<render_context> ctx) {
render_context_ = std::move(ctx);
}
/**
* @brief 标记管线需要重建
*/
void invalidate_pipeline() noexcept {
mask_pipeline_dirty_ = true;
}
/**
* @brief 设置推送常量
*/
virtual void set_push_constants(render_context& ctx) {
(void)ctx;
}
// ============================================================================================
// 成员变量
// ============================================================================================
/// 渲染上下文
std::unique_ptr<render_context> render_context_;
/// 遮罩纹理
std::shared_ptr<texture> mask_texture_;
/// 是否反相遮罩
bool invert_mask_ = false;
/// 遮罩管线
vk::Pipeline mask_pipeline_ = VK_NULL_HANDLE;
/// 遮罩管线布局
vk::PipelineLayout mask_pipeline_layout_ = VK_NULL_HANDLE;
/// 遮罩描述符池
vk::DescriptorPool mask_descriptor_pool_ = VK_NULL_HANDLE;
/// 遮罩描述符集
vk::DescriptorSet mask_descriptor_set_ = VK_NULL_HANDLE;
/// 管线是否需要重建
bool mask_pipeline_dirty_ = true;
};
// ================================================================================================
// 便捷类型定义
// ================================================================================================
/**
* @brief 创建遮罩控件
*
* @tparam T 着色器绑定类型
* @return 遮罩控件指针
*/
template<typename T>
[[nodiscard]] std::shared_ptr<mask_widget<T>> make_mask_widget() {
return std::make_shared<mask_widget<T>>();
}
} // namespace mirai

View File

@@ -670,6 +670,18 @@ public:
*/
[[nodiscard]] text_renderer* get_text_renderer() const noexcept { return text_renderer_; }
/**
* @brief 获取渲染上下文
* @return 渲染上下文指针,如果不可用返回 nullptr
*/
[[nodiscard]] class render_context* get_render_context() const noexcept { return render_context_; }
/**
* @brief 设置渲染上下文
* @param ctx 渲染上下文
*/
void set_render_context(class render_context* ctx) noexcept { render_context_ = ctx; }
private:
/**
* @brief 获取当前状态
@@ -705,6 +717,9 @@ private:
/// 文本渲染器
text_renderer* text_renderer_ = nullptr;
/// 渲染上下文
class render_context* render_context_ = nullptr;
/// 状态栈
std::stack<paint_state> state_stack_;
};

25
src/widget/CMakeLists.txt Normal file
View File

@@ -0,0 +1,25 @@
# ================================================================================================
# MIRAI Framework - Widget 模块
# 描述: shader_widget 基础类和控件系统
# ================================================================================================
project(mirai_widget)
simple_library(STATIC)
target_link_libraries(${PROJECT_NAME} PUBLIC
mirai_ui
mirai_shader
)
set_target_properties(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
EXPORT_NAME widget
OUTPUT_NAME mirai_widget
)
install(TARGETS ${PROJECT_NAME}
EXPORT mirai-targets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
message(STATUS "Configured mirai_widget library")

230
src/widget/effects.hpp Normal file
View File

@@ -0,0 +1,230 @@
/**
* @file effects.hpp
* @brief MIRAI 框架效果控件汇总头文件
* @author MIRAI Team
* @version 0.1.0
*
* 本文件提供所有效果控件的统一包含入口,方便用户一次性包含所有效果。
*
* 包含的效果控件:
* - blur_widget: 高斯模糊效果Backdrop 模式)
* - gradient_widget: 渐变效果Procedural 模式)
* - rounded_rect_mask_widget: 圆角矩形遮罩Mask 模式)
* - particle_widget: 粒子系统Mesh 模式)
*
* @example
* @code
* #include "widget/effects.hpp"
*
* using namespace mirai;
*
* // 创建各种效果控件
* auto blur = std::make_shared<blur_widget>();
* auto gradient = std::make_shared<gradient_widget>();
* auto mask = std::make_shared<rounded_rect_mask_widget>();
* auto particles = std::make_shared<particle_widget>(1000);
* @endcode
*
* @see shader_widget_base
* @see shader_widget
*/
#pragma once
// ============================================================================
// 基础类型和基类
// ============================================================================
#include "widget_types.hpp"
#include "shader_widget_base.hpp"
#include "shader_widget.hpp"
// ============================================================================
// Backdrop 模式效果
// ============================================================================
/**
* @defgroup backdrop_effects Backdrop 模式效果
* @brief 后效处理效果,可采样背景纹理进行处理
* @{
*/
#include "effects/blur_widget.hpp"
/** @} */
// ============================================================================
// Procedural 模式效果
// ============================================================================
/**
* @defgroup procedural_effects Procedural 模式效果
* @brief 程序化渲染效果,基于时间和坐标生成图形
* @{
*/
#include "effects/gradient_widget.hpp"
/** @} */
// ============================================================================
// Mask 模式效果
// ============================================================================
/**
* @defgroup mask_effects Mask 模式效果
* @brief 遮罩效果,用于裁剪和形状遮罩
* @{
*/
#include "effects/rounded_rect_mask_widget.hpp"
/** @} */
// ============================================================================
// Mesh 模式效果
// ============================================================================
/**
* @defgroup mesh_effects Mesh 模式效果
* @brief 自定义网格渲染效果,支持实例化渲染
* @{
*/
#include "effects/particle_widget.hpp"
/** @} */
// ============================================================================
// 便捷类型别名
// ============================================================================
namespace mirai {
/**
* @brief 效果控件类型别名
* @{
*/
/// 模糊效果控件指针类型
using blur_widget_ptr = std::shared_ptr<blur_widget>;
/// 渐变效果控件指针类型
using gradient_widget_ptr = std::shared_ptr<gradient_widget>;
/// 圆角遮罩控件指针类型
using rounded_rect_mask_widget_ptr = std::shared_ptr<rounded_rect_mask_widget>;
/// 粒子系统控件指针类型
using particle_widget_ptr = std::shared_ptr<particle_widget>;
/** @} */
// ============================================================================
// 工厂函数
// ============================================================================
/**
* @brief 效果控件工厂函数
* @{
*/
/**
* @brief 创建模糊效果控件
* @param radius 模糊半径(像素),默认 8.0f
* @param quality 模糊质量,默认 medium
* @return 模糊效果控件指针
*
* @example
* @code
* auto blur = make_blur_widget(10.0f, blur_quality::high);
* @endcode
*/
[[nodiscard]] inline blur_widget_ptr make_blur_widget(
f32 radius = 8.0f,
blur_quality quality = blur_quality::medium
) {
return std::make_shared<blur_widget>(radius, quality);
}
/**
* @brief 创建渐变效果控件
* @param start 起始颜色
* @param end 结束颜色
* @param type 渐变类型,默认 linear
* @return 渐变效果控件指针
*
* @example
* @code
* auto gradient = make_gradient_widget(
* color{1.0f, 0.0f, 0.0f, 1.0f},
* color{0.0f, 0.0f, 1.0f, 1.0f},
* gradient_type::radial
* );
* @endcode
*/
[[nodiscard]] inline gradient_widget_ptr make_gradient_widget(
const color& start = color{1.0f, 1.0f, 1.0f, 1.0f},
const color& end = color{0.0f, 0.0f, 0.0f, 1.0f},
gradient_type type = gradient_type::linear
) {
return std::make_shared<gradient_widget>(start, end, type);
}
/**
* @brief 创建圆角遮罩控件
* @param radius 统一圆角半径,默认 0.0f
* @return 圆角遮罩控件指针
*
* @example
* @code
* auto mask = make_rounded_rect_mask_widget(0.1f);
* @endcode
*/
[[nodiscard]] inline rounded_rect_mask_widget_ptr make_rounded_rect_mask_widget(
f32 radius = 0.0f
) {
return std::make_shared<rounded_rect_mask_widget>(radius);
}
/**
* @brief 创建圆角遮罩控件(四角独立)
* @param top_left 左上角圆角半径
* @param top_right 右上角圆角半径
* @param bottom_right 右下角圆角半径
* @param bottom_left 左下角圆角半径
* @return 圆角遮罩控件指针
*
* @example
* @code
* auto mask = make_rounded_rect_mask_widget(0.1f, 0.1f, 0.2f, 0.2f);
* @endcode
*/
[[nodiscard]] inline rounded_rect_mask_widget_ptr make_rounded_rect_mask_widget(
f32 top_left,
f32 top_right,
f32 bottom_right,
f32 bottom_left
) {
return std::make_shared<rounded_rect_mask_widget>(
top_left, top_right, bottom_right, bottom_left
);
}
/**
* @brief 创建粒子系统控件
* @param count 粒子数量,默认 100
* @return 粒子系统控件指针
*
* @example
* @code
* auto particles = make_particle_widget(1000);
* @endcode
*/
[[nodiscard]] inline particle_widget_ptr make_particle_widget(u32 count = 100) {
return std::make_shared<particle_widget>(count);
}
/** @} */
} // namespace mirai

View File

@@ -0,0 +1,164 @@
/**
* @file blur_widget.hpp
* @brief 高斯模糊效果控件
* @author MIRAI Team
* @version 0.1.0
*
* 本文件定义了 blur_widget 类,实现 Backdrop 模式的高斯模糊效果。
*
* @example
* @code
* auto blur = std::make_shared<blur_widget>();
* blur->set_blur_radius(10.0f);
* blur->set_blur_quality(blur_quality::medium);
* @endcode
*/
#pragma once
#include "shader_widget.hpp"
#include ""
#include <span>
#include <cstdint>
namespace mirai {
// ================================================================================================
// 模糊质量枚举
// ================================================================================================
/**
* @brief 模糊质量等级
*/
enum class blur_quality : u8 {
low = 1, ///< 低质量4 次采样)
medium = 2, ///< 中等质量8 次采样)
high = 3 ///< 高质量12 次采样)
};
// ================================================================================================
// blur_widget 类
// ================================================================================================
/**
* @brief 高斯模糊效果控件
*
* 继承自 backdrop_widget实现对背景的高斯模糊效果。
*
* 特点:
* - 可调节的模糊半径
* - 三档质量选择(低/中/高)
* - 使用分离式高斯模糊算法
*
* @see backdrop_widget
* @see blur_bindings
*/
class blur_widget : public backdrop_widget<blur_bindings> {
public:
MIRAI_OBJECT_TYPE_INFO(blur_widget, backdrop_widget<blur_bindings>)
// ============================================================================================
// 构造与析构
// ============================================================================================
/**
* @brief 默认构造函数
*
* 初始化默认参数:
* - blur_radius = 8.0f
* - quality = medium
*/
blur_widget()
: blur_radius_(8.0f)
, quality_(blur_quality::medium) {
}
/**
* @brief 带参数的构造函数
* @param radius 模糊半径(像素)
* @param quality 模糊质量
*/
explicit blur_widget(f32 radius, blur_quality quality = blur_quality::medium)
: blur_radius_(radius)
, quality_(quality) {
}
/**
* @brief 虚析构函数
*/
~blur_widget() override = default;
// ============================================================================================
// 参数设置
// ============================================================================================
/**
* @brief 设置模糊半径
* @param radius 模糊半径(像素),范围 [0, 100]
*/
void set_blur_radius(f32 radius) {
blur_radius_ = std::clamp(radius, 0.0f, 100.0f);
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取模糊半径
* @return 当前模糊半径
*/
[[nodiscard]] f32 blur_radius() const noexcept { return blur_radius_; }
/**
* @brief 设置模糊质量
* @param quality 模糊质量等级
*/
void set_blur_quality(blur_quality quality) {
quality_ = quality;
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取模糊质量
* @return 当前模糊质量
*/
[[nodiscard]] blur_quality quality() const noexcept { return quality_; }
protected:
// ============================================================================================
// 重写基类方法
// ============================================================================================
/**
* @brief 更新 Uniform Buffer
*
* 将模糊参数写入 GPU 缓冲区。
*/
void update_uniform_buffer() override {
// BlurParams 结构体布局(与 blur.slang 中定义一致)
struct BlurParams {
f32 blur_radius;
f32 blur_quality;
f32 _padding[2];
};
BlurParams params{};
params.blur_radius = blur_radius_;
params.blur_quality = static_cast<f32>(quality_);
// TODO: 将 params 写入 uniform buffer
// uniform_buffer_->write(&params, sizeof(params));
}
private:
// ============================================================================================
// 成员变量
// ============================================================================================
/// 模糊半径(像素)
f32 blur_radius_;
/// 模糊质量
blur_quality quality_;
};
} // namespace mirai

View File

@@ -0,0 +1,372 @@
/**
* @file gradient_widget.hpp
* @brief 渐变效果控件
* @author MIRAI Team
* @version 0.1.0
*
* 本文件定义了 gradient_widget 类,实现 Procedural 模式的渐变效果。
*
* @example
* @code
* auto gradient = std::make_shared<gradient_widget>();
* gradient->set_start_color(color::red());
* gradient->set_end_color(color::blue());
* gradient->set_gradient_type(gradient_type::radial);
* @endcode
*/
#pragma once
#include "shader_widget.hpp"
#include "types/color.h"
#include <span>
#include <cstdint>
#include <cmath>
namespace mirai {
// ================================================================================================
// 渐变类型枚举
// ================================================================================================
/**
* @brief 渐变类型
*/
enum class gradient_type : u32 {
linear = 0, ///< 线性渐变
radial = 1, ///< 径向渐变
angular = 2, ///< 角度渐变(圆锥渐变)
diamond = 3 ///< 菱形渐变
};
// ================================================================================================
// Gradient 绑定类型(模拟代码生成器输出)
// ================================================================================================
/**
* @brief gradient_widget 的着色器绑定类型
*
* 此类型模拟代码生成器的输出,提供:
* - SPIR-V 字节码访问
* - 绑定点常量
* - 静态反射信息
*
* @note 在实际项目中,此类型由 shader_codegen 工具自动生成
*/
struct gradient_bindings {
// ============================================================================================
// 着色器存在性标记
// ============================================================================================
static constexpr bool HAS_VERTEX_SHADER = true;
static constexpr bool HAS_FRAGMENT_SHADER = true;
// ============================================================================================
// Procedural 模式特有标记
// ============================================================================================
static constexpr bool HAS_FRAME_DATA = true;
static constexpr u32 FRAME_DATA_BINDING = 0;
static constexpr u32 FRAME_DATA_SET = 0;
// ============================================================================================
// SPIR-V 数据访问(占位实现)
// ============================================================================================
/**
* @brief 获取顶点着色器 SPIR-V 数据
* @return SPIR-V 字节码的 span
* @note 实际实现中,此数据由编译后的着色器提供
*/
[[nodiscard]] static std::span<const u32> get_vertex_spirv() {
// 占位:实际数据由着色器编译生成
static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number
return std::span<const u32>(placeholder);
}
/**
* @brief 获取片段着色器 SPIR-V 数据
* @return SPIR-V 字节码的 span
* @note 实际实现中,此数据由编译后的着色器提供
*/
[[nodiscard]] static std::span<const u32> get_fragment_spirv() {
// 占位:实际数据由着色器编译生成
static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number
return std::span<const u32>(placeholder);
}
};
// ================================================================================================
// gradient_widget 类
// ================================================================================================
/**
* @brief 渐变效果控件
*
* 继承自 procedural_widget实现程序化渐变效果。
*
* 特点:
* - 支持四种渐变类型(线性、径向、角度、菱形)
* - 可自定义起始和结束颜色
* - 支持渐变角度和中心点设置
* - 支持重复模式
*
* @see procedural_widget
* @see gradient_bindings
*/
class gradient_widget : public procedural_widget<gradient_bindings> {
public:
MIRAI_OBJECT_TYPE_INFO(gradient_widget, procedural_widget<gradient_bindings>)
// ============================================================================================
// 构造与析构
// ============================================================================================
/**
* @brief 默认构造函数
*
* 初始化默认参数:
* - start_color = 白色
* - end_color = 黑色
* - type = linear
* - angle = 0 (水平)
*/
gradient_widget()
: start_color_{1.0f, 1.0f, 1.0f, 1.0f}
, end_color_{0.0f, 0.0f, 0.0f, 1.0f}
, type_(gradient_type::linear)
, angle_(0.0f)
, center_{0.5f, 0.5f}
, radius_(0.5f)
, repeat_count_(0.0f) {
}
/**
* @brief 带颜色参数的构造函数
* @param start 起始颜色
* @param end 结束颜色
* @param type 渐变类型
*/
gradient_widget(const color& start, const color& end, gradient_type type = gradient_type::linear)
: start_color_(start)
, end_color_(end)
, type_(type)
, angle_(0.0f)
, center_{0.5f, 0.5f}
, radius_(0.5f)
, repeat_count_(0.0f) {
}
/**
* @brief 虚析构函数
*/
~gradient_widget() override = default;
// ============================================================================================
// 颜色设置
// ============================================================================================
/**
* @brief 设置起始颜色
* @param c 起始颜色
*/
void set_start_color(const color& c) {
start_color_ = c;
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取起始颜色
* @return 当前起始颜色
*/
[[nodiscard]] const color& start_color() const noexcept { return start_color_; }
/**
* @brief 设置结束颜色
* @param c 结束颜色
*/
void set_end_color(const color& c) {
end_color_ = c;
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取结束颜色
* @return 当前结束颜色
*/
[[nodiscard]] const color& end_color() const noexcept { return end_color_; }
/**
* @brief 同时设置起始和结束颜色
* @param start 起始颜色
* @param end 结束颜色
*/
void set_colors(const color& start, const color& end) {
start_color_ = start;
end_color_ = end;
mark_dirty(widget_dirty_flags::uniform_buffer);
}
// ============================================================================================
// 渐变类型设置
// ============================================================================================
/**
* @brief 设置渐变类型
* @param type 渐变类型
*/
void set_gradient_type(gradient_type type) {
type_ = type;
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取渐变类型
* @return 当前渐变类型
*/
[[nodiscard]] gradient_type type() const noexcept { return type_; }
// ============================================================================================
// 渐变参数设置
// ============================================================================================
/**
* @brief 设置渐变角度(用于线性渐变)
* @param angle_degrees 角度度数0 = 水平向右
*/
void set_angle(f32 angle_degrees) {
angle_ = angle_degrees * (3.14159265358979323846f / 180.0f);
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取渐变角度
* @return 当前角度(弧度)
*/
[[nodiscard]] f32 angle() const noexcept { return angle_; }
/**
* @brief 设置渐变中心(用于径向/角度渐变)
* @param x 中心 X 坐标 (0-1)
* @param y 中心 Y 坐标 (0-1)
*/
void set_center(f32 x, f32 y) {
center_[0] = x;
center_[1] = y;
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取渐变中心 X 坐标
* @return 中心 X 坐标
*/
[[nodiscard]] f32 center_x() const noexcept { return center_[0]; }
/**
* @brief 获取渐变中心 Y 坐标
* @return 中心 Y 坐标
*/
[[nodiscard]] f32 center_y() const noexcept { return center_[1]; }
/**
* @brief 设置渐变半径(用于径向渐变)
* @param r 半径 (0-1)
*/
void set_radius(f32 r) {
radius_ = std::max(0.001f, r);
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取渐变半径
* @return 当前半径
*/
[[nodiscard]] f32 radius() const noexcept { return radius_; }
/**
* @brief 设置重复次数
* @param count 重复次数0 = 不重复)
*/
void set_repeat_count(f32 count) {
repeat_count_ = std::max(0.0f, count);
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取重复次数
* @return 当前重复次数
*/
[[nodiscard]] f32 repeat_count() const noexcept { return repeat_count_; }
protected:
// ============================================================================================
// 重写基类方法
// ============================================================================================
/**
* @brief 更新 Uniform Buffer
*
* 将渐变参数写入 GPU 缓冲区。
*/
void update_uniform_buffer() override {
// GradientParams 结构体布局(与 gradient.slang 中定义一致)
struct GradientParams {
f32 start_color[4];
f32 end_color[4];
f32 angle;
u32 gradient_type;
f32 center[2];
f32 radius;
f32 repeat_count;
f32 _padding[2];
};
GradientParams params{};
params.start_color[0] = start_color_.r;
params.start_color[1] = start_color_.g;
params.start_color[2] = start_color_.b;
params.start_color[3] = start_color_.a;
params.end_color[0] = end_color_.r;
params.end_color[1] = end_color_.g;
params.end_color[2] = end_color_.b;
params.end_color[3] = end_color_.a;
params.angle = angle_;
params.gradient_type = static_cast<u32>(type_);
params.center[0] = center_[0];
params.center[1] = center_[1];
params.radius = radius_;
params.repeat_count = repeat_count_;
// TODO: 将 params 写入 uniform buffer
// uniform_buffer_->write(&params, sizeof(params));
}
private:
// ============================================================================================
// 成员变量
// ============================================================================================
/// 起始颜色
color start_color_;
/// 结束颜色
color end_color_;
/// 渐变类型
gradient_type type_;
/// 渐变角度(弧度)
f32 angle_;
/// 渐变中心
f32 center_[2];
/// 渐变半径
f32 radius_;
/// 重复次数
f32 repeat_count_;
};
} // namespace mirai

View File

@@ -0,0 +1,485 @@
/**
* @file particle_widget.hpp
* @brief 粒子系统控件
* @author MIRAI Team
* @version 0.1.0
*
* 本文件定义了 particle_widget 类,实现 Mesh 模式的粒子渲染效果。
*
* @example
* @code
* auto particles = std::make_shared<particle_widget>();
* particles->set_particle_count(1000);
* particles->set_particle_size(5.0f, 1.0f); // 起始大小 -> 结束大小
* particles->set_colors(color::yellow(), color::red());
* particles->set_particle_mode(particle_mode::billboard);
* @endcode
*/
#pragma once
#include "shader_widget.hpp"
#include "types/color.h"
#include <span>
#include <cstdint>
#include <vector>
#include <algorithm>
namespace mirai {
// ================================================================================================
// 粒子渲染模式枚举
// ================================================================================================
/**
* @brief 粒子渲染模式
*/
enum class particle_mode : u32 {
billboard = 0, ///< Billboard 模式(始终面向相机)
velocity_aligned = 1, ///< 速度对齐模式
stretched = 2 ///< 拉伸模式(沿速度方向拉伸)
};
/**
* @brief 粒子形状
*/
enum class particle_shape : u8 {
textured, ///< 使用纹理
circle, ///< 圆形(无纹理)
soft, ///< 软边缘圆形
glow ///< 发光效果
};
// ================================================================================================
// 粒子实例数据
// ================================================================================================
/**
* @brief 单个粒子的实例数据
*
* 与 mesh.slang 中的 ParticleInstance 结构体对应
*/
struct particle_instance {
f32 position[3]; ///< 粒子位置
f32 size; ///< 粒子大小
f32 color[4]; ///< 粒子颜色
f32 rotation; ///< 粒子旋转
f32 life; ///< 生命周期 (0-1)
f32 velocity[2]; ///< 速度
};
// ================================================================================================
// Particle 绑定类型(模拟代码生成器输出)
// ================================================================================================
/**
* @brief particle_widget 的着色器绑定类型
*
* 此类型模拟代码生成器的输出,提供:
* - SPIR-V 字节码访问
* - 绑定点常量
* - 顶点类型定义
*
* @note 在实际项目中,此类型由 shader_codegen 工具自动生成
*/
struct particle_bindings {
// ============================================================================================
// 着色器存在性标记
// ============================================================================================
static constexpr bool HAS_VERTEX_SHADER = true;
static constexpr bool HAS_FRAGMENT_SHADER = true;
// ============================================================================================
// Mesh 模式特有标记
// ============================================================================================
static constexpr bool HAS_CUSTOM_VERTEX_INPUT = true;
/// 顶点类型定义
using VertexType = particle_instance;
// ============================================================================================
// SPIR-V 数据访问(占位实现)
// ============================================================================================
/**
* @brief 获取顶点着色器 SPIR-V 数据
* @return SPIR-V 字节码的 span
* @note 实际实现中,此数据由编译后的着色器提供
*/
[[nodiscard]] static std::span<const u32> get_vertex_spirv() {
// 占位:实际数据由着色器编译生成
static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number
return std::span<const u32>(placeholder);
}
/**
* @brief 获取片段着色器 SPIR-V 数据
* @return SPIR-V 字节码的 span
* @note 实际实现中,此数据由编译后的着色器提供
*/
[[nodiscard]] static std::span<const u32> get_fragment_spirv() {
// 占位:实际数据由着色器编译生成
static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number
return std::span<const u32>(placeholder);
}
};
// ================================================================================================
// particle_widget 类
// ================================================================================================
/**
* @brief 粒子系统控件
*
* 继承自 mesh_widget使用实例化渲染绘制大量粒子。
*
* 特点:
* - 支持大量粒子的高效渲染
* - 多种渲染模式Billboard、速度对齐、拉伸
* - 生命周期颜色和大小动画
* - 淡入淡出效果
*
* @see mesh_widget
* @see particle_bindings
*/
class particle_widget : public mesh_widget<particle_bindings> {
public:
MIRAI_OBJECT_TYPE_INFO(particle_widget, mesh_widget<particle_bindings>)
// ============================================================================================
// 构造与析构
// ============================================================================================
/**
* @brief 默认构造函数
*
* 初始化默认参数:
* - particle_count = 100
* - size_start = 10.0f
* - size_end = 2.0f
* - color_start = 白色
* - color_end = 透明白色
*/
particle_widget()
: particle_count_(100)
, size_start_(10.0f)
, size_end_(2.0f)
, color_start_{1.0f, 1.0f, 1.0f, 1.0f}
, color_end_{1.0f, 1.0f, 1.0f, 0.0f}
, fade_in_(0.1f)
, fade_out_(0.3f)
, mode_(particle_mode::billboard)
, shape_(particle_shape::circle)
, stretch_factor_(0.5f) {
}
/**
* @brief 带粒子数量的构造函数
* @param count 粒子数量
*/
explicit particle_widget(u32 count)
: particle_count_(count)
, size_start_(10.0f)
, size_end_(2.0f)
, color_start_{1.0f, 1.0f, 1.0f, 1.0f}
, color_end_{1.0f, 1.0f, 1.0f, 0.0f}
, fade_in_(0.1f)
, fade_out_(0.3f)
, mode_(particle_mode::billboard)
, shape_(particle_shape::circle)
, stretch_factor_(0.5f) {
}
/**
* @brief 虚析构函数
*/
~particle_widget() override = default;
// ============================================================================================
// 粒子数量设置
// ============================================================================================
/**
* @brief 设置粒子数量
* @param count 粒子数量
*/
void set_particle_count(u32 count) {
particle_count_ = count;
particles_.resize(count);
mark_dirty(widget_dirty_flags::vertex_buffer);
}
/**
* @brief 获取粒子数量
* @return 当前粒子数量
*/
[[nodiscard]] u32 particle_count() const noexcept { return particle_count_; }
// ============================================================================================
// 大小设置
// ============================================================================================
/**
* @brief 设置粒子大小范围
* @param start 起始大小(刚出生时)
* @param end 结束大小(死亡时)
*/
void set_particle_size(f32 start, f32 end) {
size_start_ = std::max(0.0f, start);
size_end_ = std::max(0.0f, end);
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 设置统一粒子大小
* @param size 粒子大小
*/
void set_particle_size(f32 size) {
set_particle_size(size, size);
}
/**
* @brief 获取起始大小
* @return 起始大小
*/
[[nodiscard]] f32 size_start() const noexcept { return size_start_; }
/**
* @brief 获取结束大小
* @return 结束大小
*/
[[nodiscard]] f32 size_end() const noexcept { return size_end_; }
// ============================================================================================
// 颜色设置
// ============================================================================================
/**
* @brief 设置粒子颜色范围
* @param start 起始颜色(刚出生时)
* @param end 结束颜色(死亡时)
*/
void set_colors(const color& start, const color& end) {
color_start_ = start;
color_end_ = end;
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 设置起始颜色
* @param c 起始颜色
*/
void set_color_start(const color& c) {
color_start_ = c;
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 设置结束颜色
* @param c 结束颜色
*/
void set_color_end(const color& c) {
color_end_ = c;
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取起始颜色
* @return 起始颜色
*/
[[nodiscard]] const color& color_start() const noexcept { return color_start_; }
/**
* @brief 获取结束颜色
* @return 结束颜色
*/
[[nodiscard]] const color& color_end() const noexcept { return color_end_; }
// ============================================================================================
// 淡入淡出设置
// ============================================================================================
/**
* @brief 设置淡入淡出时间
* @param fade_in 淡入时间比例 (0-1)
* @param fade_out 淡出时间比例 (0-1)
*/
void set_fade(f32 fade_in, f32 fade_out) {
fade_in_ = std::clamp(fade_in, 0.0f, 1.0f);
fade_out_ = std::clamp(fade_out, 0.0f, 1.0f);
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取淡入时间
* @return 淡入时间比例
*/
[[nodiscard]] f32 fade_in() const noexcept { return fade_in_; }
/**
* @brief 获取淡出时间
* @return 淡出时间比例
*/
[[nodiscard]] f32 fade_out() const noexcept { return fade_out_; }
// ============================================================================================
// 渲染模式设置
// ============================================================================================
/**
* @brief 设置粒子渲染模式
* @param mode 渲染模式
*/
void set_particle_mode(particle_mode mode) {
mode_ = mode;
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取粒子渲染模式
* @return 当前渲染模式
*/
[[nodiscard]] particle_mode mode() const noexcept { return mode_; }
/**
* @brief 设置粒子形状
* @param shape 粒子形状
*/
void set_particle_shape(particle_shape shape) {
shape_ = shape;
mark_dirty(widget_dirty_flags::pipeline);
}
/**
* @brief 获取粒子形状
* @return 当前粒子形状
*/
[[nodiscard]] particle_shape shape() const noexcept { return shape_; }
/**
* @brief 设置拉伸因子
* @param factor 拉伸因子(用于速度拉伸模式)
*/
void set_stretch_factor(f32 factor) {
stretch_factor_ = std::max(0.0f, factor);
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取拉伸因子
* @return 当前拉伸因子
*/
[[nodiscard]] f32 stretch_factor() const noexcept { return stretch_factor_; }
// ============================================================================================
// 粒子数据访问
// ============================================================================================
/**
* @brief 获取粒子数据(可修改)
* @return 粒子数据向量的引用
*/
[[nodiscard]] std::vector<particle_instance>& particles() { return particles_; }
/**
* @brief 获取粒子数据(只读)
* @return 粒子数据向量的常量引用
*/
[[nodiscard]] const std::vector<particle_instance>& particles() const { return particles_; }
/**
* @brief 标记粒子数据已更新
*
* 在修改粒子数据后调用此方法以触发 GPU 缓冲区更新
*/
void mark_particles_dirty() {
mark_dirty(widget_dirty_flags::vertex_buffer);
}
protected:
// ============================================================================================
// 重写基类方法
// ============================================================================================
/**
* @brief 更新 Uniform Buffer
*
* 将粒子系统参数写入 GPU 缓冲区。
*/
void update_uniform_buffer() override {
// ParticleSystemParams 结构体布局(与 particle.slang 中定义一致)
struct ParticleSystemParams {
f32 color_start[4];
f32 color_end[4];
f32 size_start;
f32 size_end;
f32 fade_in;
f32 fade_out;
u32 particle_mode;
f32 stretch_factor;
f32 _padding[2];
};
ParticleSystemParams params{};
params.color_start[0] = color_start_.r;
params.color_start[1] = color_start_.g;
params.color_start[2] = color_start_.b;
params.color_start[3] = color_start_.a;
params.color_end[0] = color_end_.r;
params.color_end[1] = color_end_.g;
params.color_end[2] = color_end_.b;
params.color_end[3] = color_end_.a;
params.size_start = size_start_;
params.size_end = size_end_;
params.fade_in = fade_in_;
params.fade_out = fade_out_;
params.particle_mode = static_cast<u32>(mode_);
params.stretch_factor = stretch_factor_;
// TODO: 将 params 写入 uniform buffer
// uniform_buffer_->write(&params, sizeof(params));
}
private:
// ============================================================================================
// 成员变量
// ============================================================================================
/// 粒子数量
u32 particle_count_;
/// 起始大小
f32 size_start_;
/// 结束大小
f32 size_end_;
/// 起始颜色
color color_start_;
/// 结束颜色
color color_end_;
/// 淡入时间比例
f32 fade_in_;
/// 淡出时间比例
f32 fade_out_;
/// 渲染模式
particle_mode mode_;
/// 粒子形状
particle_shape shape_;
/// 拉伸因子
f32 stretch_factor_;
/// 粒子实例数据
std::vector<particle_instance> particles_;
};
} // namespace mirai

View File

@@ -0,0 +1,313 @@
/**
* @file rounded_rect_mask_widget.hpp
* @brief 圆角矩形遮罩控件
* @author MIRAI Team
* @version 0.1.0
*
* 本文件定义了 rounded_rect_mask_widget 类,实现 Mask 模式的圆角矩形遮罩效果。
*
* @example
* @code
* auto mask = std::make_shared<rounded_rect_mask_widget>();
* mask->set_corner_radius(10.0f); // 统一圆角
* // 或者设置四角独立圆角
* mask->set_corner_radius(10.0f, 20.0f, 10.0f, 20.0f);
* mask->set_feather(2.0f); // 边缘羽化
* @endcode
*/
#pragma once
#include "shader_widget.hpp"
#include "types/corner_radius.h"
#include <span>
#include <cstdint>
#include <algorithm>
namespace mirai {
// ================================================================================================
// RoundedRectMask 绑定类型(模拟代码生成器输出)
// ================================================================================================
/**
* @brief rounded_rect_mask_widget 的着色器绑定类型
*
* 此类型模拟代码生成器的输出,提供:
* - SPIR-V 字节码访问
* - 绑定点常量
* - 静态反射信息
*
* @note 在实际项目中,此类型由 shader_codegen 工具自动生成
*/
struct rounded_rect_mask_bindings {
// ============================================================================================
// 着色器存在性标记
// ============================================================================================
static constexpr bool HAS_VERTEX_SHADER = true;
static constexpr bool HAS_FRAGMENT_SHADER = true;
// ============================================================================================
// Mask 模式特有标记
// ============================================================================================
static constexpr bool HAS_MASK_SAMPLER = true;
static constexpr u32 MASK_BINDING = 0;
static constexpr u32 MASK_SET = 0;
// ============================================================================================
// SPIR-V 数据访问(占位实现)
// ============================================================================================
/**
* @brief 获取顶点着色器 SPIR-V 数据
* @return SPIR-V 字节码的 span
* @note 实际实现中,此数据由编译后的着色器提供
*/
[[nodiscard]] static std::span<const u32> get_vertex_spirv() {
// 占位:实际数据由着色器编译生成
static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number
return std::span<const u32>(placeholder);
}
/**
* @brief 获取片段着色器 SPIR-V 数据
* @return SPIR-V 字节码的 span
* @note 实际实现中,此数据由编译后的着色器提供
*/
[[nodiscard]] static std::span<const u32> get_fragment_spirv() {
// 占位:实际数据由着色器编译生成
static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number
return std::span<const u32>(placeholder);
}
};
// ================================================================================================
// rounded_rect_mask_widget 类
// ================================================================================================
/**
* @brief 圆角矩形遮罩控件
*
* 继承自 mask_widget使用 SDF 生成圆角矩形遮罩。
*
* 特点:
* - 支持四角独立圆角半径
* - 可调节的边缘羽化
* - 支持边框模式
* - 高质量抗锯齿边缘
*
* @see mask_widget
* @see rounded_rect_mask_bindings
*/
class rounded_rect_mask_widget : public mask_widget<rounded_rect_mask_bindings> {
public:
MIRAI_OBJECT_TYPE_INFO(rounded_rect_mask_widget, mask_widget<rounded_rect_mask_bindings>)
// ============================================================================================
// 构造与析构
// ============================================================================================
/**
* @brief 默认构造函数
*
* 初始化默认参数:
* - corner_radius = 0 (无圆角)
* - feather = 0 (无羽化)
* - border_width = 0 (填充模式)
*/
rounded_rect_mask_widget()
: corner_radius_{0.0f, 0.0f, 0.0f, 0.0f}
, feather_(0.0f)
, border_width_(0.0f) {
}
/**
* @brief 带统一圆角的构造函数
* @param radius 统一圆角半径
*/
explicit rounded_rect_mask_widget(f32 radius)
: corner_radius_{radius, radius, radius, radius}
, feather_(0.0f)
, border_width_(0.0f) {
}
/**
* @brief 带四角独立圆角的构造函数
* @param top_left 左上角圆角半径
* @param top_right 右上角圆角半径
* @param bottom_right 右下角圆角半径
* @param bottom_left 左下角圆角半径
*/
rounded_rect_mask_widget(f32 top_left, f32 top_right, f32 bottom_right, f32 bottom_left)
: corner_radius_{top_left, top_right, bottom_right, bottom_left}
, feather_(0.0f)
, border_width_(0.0f) {
}
/**
* @brief 虚析构函数
*/
~rounded_rect_mask_widget() override = default;
// ============================================================================================
// 圆角设置
// ============================================================================================
/**
* @brief 设置统一圆角半径
* @param radius 圆角半径(相对于控件尺寸,范围 0-0.5
*/
void set_corner_radius(f32 radius) {
radius = std::clamp(radius, 0.0f, 0.5f);
corner_radius_[0] = radius;
corner_radius_[1] = radius;
corner_radius_[2] = radius;
corner_radius_[3] = radius;
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 设置四角独立圆角半径
* @param top_left 左上角圆角半径
* @param top_right 右上角圆角半径
* @param bottom_right 右下角圆角半径
* @param bottom_left 左下角圆角半径
*/
void set_corner_radius(f32 top_left, f32 top_right, f32 bottom_right, f32 bottom_left) {
corner_radius_[0] = std::clamp(top_left, 0.0f, 0.5f);
corner_radius_[1] = std::clamp(top_right, 0.0f, 0.5f);
corner_radius_[2] = std::clamp(bottom_right, 0.0f, 0.5f);
corner_radius_[3] = std::clamp(bottom_left, 0.0f, 0.5f);
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 使用 corner_radius 类型设置圆角
* @param radius corner_radius 对象
*/
void set_corner_radius(const corner_radius& radius) {
corner_radius_[0] = std::clamp(radius.top_left, 0.0f, 0.5f);
corner_radius_[1] = std::clamp(radius.top_right, 0.0f, 0.5f);
corner_radius_[2] = std::clamp(radius.bottom_right, 0.0f, 0.5f);
corner_radius_[3] = std::clamp(radius.bottom_left, 0.0f, 0.5f);
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取左上角圆角半径
* @return 左上角圆角半径
*/
[[nodiscard]] f32 top_left_radius() const noexcept { return corner_radius_[0]; }
/**
* @brief 获取右上角圆角半径
* @return 右上角圆角半径
*/
[[nodiscard]] f32 top_right_radius() const noexcept { return corner_radius_[1]; }
/**
* @brief 获取右下角圆角半径
* @return 右下角圆角半径
*/
[[nodiscard]] f32 bottom_right_radius() const noexcept { return corner_radius_[2]; }
/**
* @brief 获取左下角圆角半径
* @return 左下角圆角半径
*/
[[nodiscard]] f32 bottom_left_radius() const noexcept { return corner_radius_[3]; }
// ============================================================================================
// 羽化设置
// ============================================================================================
/**
* @brief 设置边缘羽化量
* @param feather 羽化量(相对于控件尺寸,范围 0-0.5
*/
void set_feather(f32 feather) {
feather_ = std::clamp(feather, 0.0f, 0.5f);
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取边缘羽化量
* @return 当前羽化量
*/
[[nodiscard]] f32 feather() const noexcept { return feather_; }
// ============================================================================================
// 边框设置
// ============================================================================================
/**
* @brief 设置边框宽度
* @param width 边框宽度0 = 填充模式)
*/
void set_border_width(f32 width) {
border_width_ = std::max(0.0f, width);
mark_dirty(widget_dirty_flags::uniform_buffer);
}
/**
* @brief 获取边框宽度
* @return 当前边框宽度
*/
[[nodiscard]] f32 border_width() const noexcept { return border_width_; }
/**
* @brief 检查是否为边框模式
* @return 如果边框宽度大于 0 返回 true
*/
[[nodiscard]] bool is_border_mode() const noexcept { return border_width_ > 0.0f; }
protected:
// ============================================================================================
// 重写基类方法
// ============================================================================================
/**
* @brief 更新 Uniform Buffer
*
* 将遮罩参数写入 GPU 缓冲区。
*/
void update_uniform_buffer() override {
// RoundedRectMaskParams 结构体布局(与 rounded_rect_mask.slang 中定义一致)
struct RoundedRectMaskParams {
f32 corner_radius[4];
f32 feather;
f32 border_width;
f32 _padding[2];
};
RoundedRectMaskParams params{};
params.corner_radius[0] = corner_radius_[0];
params.corner_radius[1] = corner_radius_[1];
params.corner_radius[2] = corner_radius_[2];
params.corner_radius[3] = corner_radius_[3];
params.feather = feather_;
params.border_width = border_width_;
// TODO: 将 params 写入 uniform buffer
// uniform_buffer_->write(&params, sizeof(params));
}
private:
// ============================================================================================
// 成员变量
// ============================================================================================
/// 四角圆角半径 (top_left, top_right, bottom_right, bottom_left)
f32 corner_radius_[4];
/// 边缘羽化量
f32 feather_;
/// 边框宽度0 = 填充模式)
f32 border_width_;
};
} // namespace mirai

View File

@@ -0,0 +1,421 @@
/**
* @file shader_widget.hpp
* @brief MIRAI 框架 shader_widget 模板化基类
* @author MIRAI Team
* @version 0.1.0
*
* 本文件定义了 shader_widget 模板化基类,包括:
* - 类型安全的着色器资源管理
* - 编译时绑定类型检查
* - 静态反射信息访问
*/
#pragma once
#include "shader_widget_base.hpp"
#include "widget_types.hpp"
#include "shader_types.hpp"
#include "pipeline.hpp"
#include "buffer.hpp"
#include "descriptor_pool.hpp"
#include <memory>
#include <vector>
namespace mirai {
// ================================================================================================
// shader_widget 模板类
// ================================================================================================
/**
* @brief 模板化的 shader_widget 基类
*
* @tparam Bindings 着色器绑定类型,必须满足 shader_bindings concept
*
* 这是所有着色器控件的基类模板。子类通过继承此类并指定绑定类型,
* 获得类型安全的着色器资源管理能力。
*
* Bindings 类型必须提供:
* - `HAS_VERTEX_SHADER` 静态常量
* - `HAS_FRAGMENT_SHADER` 静态常量
* - `get_vertex_spirv()` 静态方法
* - `get_fragment_spirv()` 静态方法
*
* @example
* @code
* // 假设 blur_shader_bindings 是由代码生成器生成的绑定类
* class blur_widget : public shader_widget<blur_shader_bindings> {
* public:
* void set_radius(float radius) {
* params_.radius = radius;
* mark_dirty(widget_dirty_flags::uniform_buffer);
* }
* protected:
* void update_uniform_buffer() override {
* // 更新 uniform buffer 数据
* }
* };
* @endcode
*/
template<typename Bindings>
requires shader_bindings<Bindings>
class shader_widget : public shader_widget_base {
public:
MIRAI_OBJECT_TYPE_INFO(shader_widget, shader_widget_base)
/// 绑定类型别名
using bindings_type = Bindings;
// 编译时验证
static_assert(Bindings::HAS_VERTEX_SHADER, "Bindings must have vertex shader");
static_assert(Bindings::HAS_FRAGMENT_SHADER, "Bindings must have fragment shader");
/**
* @brief 默认构造函数
*/
shader_widget() = default;
/**
* @brief 虚析构函数
*/
~shader_widget() override = default;
// ============================================================================================
// 静态访问器
// ============================================================================================
/**
* @brief 获取顶点着色器 SPIR-V 数据
* @return SPIR-V 字节码的 span
*/
[[nodiscard]] static std::span<const u32> vertex_spirv() {
return Bindings::get_vertex_spirv();
}
/**
* @brief 获取片段着色器 SPIR-V 数据
* @return SPIR-V 字节码的 span
*/
[[nodiscard]] static std::span<const u32> fragment_spirv() {
return Bindings::get_fragment_spirv();
}
/**
* @brief 获取静态反射信息
*
* 如果 Bindings 类型提供了 get_reflection() 方法,则返回反射信息;
* 否则返回空的反射信息。
*
* @return 静态着色器反射信息
*/
[[nodiscard]] static const static_shader_reflection& reflection() {
if constexpr (requires { Bindings::get_reflection(); }) {
return Bindings::get_reflection();
} else {
static const static_shader_reflection empty_reflection{};
return empty_reflection;
}
}
protected:
// ============================================================================================
// 资源访问
// ============================================================================================
/**
* @brief 获取管线句柄
* @return Vulkan 管线句柄
*/
[[nodiscard]] VkPipeline pipeline() const noexcept { return pipeline_; }
/**
* @brief 获取管线布局句柄
* @return Vulkan 管线布局句柄
*/
[[nodiscard]] VkPipelineLayout pipeline_layout() const noexcept { return pipeline_layout_; }
/**
* @brief 获取描述符集
* @param index 描述符集索引
* @return Vulkan 描述符集句柄,如果索引超出范围返回 VK_NULL_HANDLE
*/
[[nodiscard]] VkDescriptorSet descriptor_set(u32 index = 0) const noexcept {
return index < descriptor_sets_.size() ? descriptor_sets_[index] : VK_NULL_HANDLE;
}
/**
* @brief 获取描述符集数量
* @return 描述符集数量
*/
[[nodiscard]] size_type descriptor_set_count() const noexcept {
return descriptor_sets_.size();
}
// ============================================================================================
// 默认实现
// ============================================================================================
/**
* @brief 创建管线(默认实现)
*
* 子类可以重写此方法以自定义管线创建。
* 默认实现使用 vertex_spirv() 和 fragment_spirv() 创建标准的图形管线。
*/
void create_pipeline() override {
// 获取 SPIR-V 数据
[[maybe_unused]] auto vert_spirv = vertex_spirv();
[[maybe_unused]] auto frag_spirv = fragment_spirv();
// TODO: 使用 pipeline 类创建管线
// 这里需要根据实际的 pipeline 类接口实现
//
// 示例伪代码:
// pipeline_create_info info;
// info.vertex_spirv = vert_spirv;
// info.fragment_spirv = frag_spirv;
// info.descriptor_set_layouts = ...;
// pipeline_ = create_graphics_pipeline(device(), info);
// pipeline_layout_ = ...;
}
/**
* @brief 创建描述符集(默认实现)
*
* 子类可以重写此方法以自定义描述符集创建。
* 默认实现根据反射信息创建描述符集。
*/
void create_descriptor_sets() override {
// TODO: 使用 descriptor_pool 创建描述符集
// 这里需要根据实际的 descriptor_pool 类接口实现
//
// 示例伪代码:
// auto& refl = reflection();
// for (const auto& block : refl.uniform_blocks) {
// // 创建描述符集布局
// // 分配描述符集
// }
}
/**
* @brief 更新 Uniform Buffer默认实现
*
* 子类必须实现此方法来更新自己的 uniform 数据。
* 默认为空实现。
*/
void update_uniform_buffer() override {
// 子类必须实现此方法
// 默认为空实现
}
/**
* @brief 记录绘制命令(默认实现)
*
* 子类可以重写此方法以自定义绘制逻辑。
* 默认实现绑定管线和描述符集,绘制全屏四边形。
*
* @param cmd 命令缓冲区
*/
void draw(command_buffer& cmd) override {
// TODO: 实现默认绘制逻辑
//
// 示例伪代码:
// cmd.bind_pipeline(vk::PipelineBindPoint::eGraphics, pipeline_);
// cmd.bind_descriptor_sets(pipeline_layout_, 0, descriptor_sets_);
// cmd.draw(6, 1, 0, 0); // 全屏四边形(两个三角形)
}
/**
* @brief 销毁资源(重写)
*
* 清理模板类特有的资源。
*/
void destroy() override {
// 清理 Vulkan 资源
// 注意:实际的资源销毁需要 VkDevice
// 这里只是重置句柄
if (pipeline_ != VK_NULL_HANDLE) {
// TODO: vkDestroyPipeline(device().get_handle(), pipeline_, nullptr);
pipeline_ = VK_NULL_HANDLE;
}
if (pipeline_layout_ != VK_NULL_HANDLE) {
// TODO: vkDestroyPipelineLayout(device().get_handle(), pipeline_layout_, nullptr);
pipeline_layout_ = VK_NULL_HANDLE;
}
// 描述符集由描述符池管理,通常不需要手动释放
descriptor_sets_.clear();
// 调用基类销毁
shader_widget_base::destroy();
}
protected:
// ============================================================================================
// 成员变量
// ============================================================================================
/// Vulkan 管线句柄
VkPipeline pipeline_ = VK_NULL_HANDLE;
/// Vulkan 管线布局句柄
VkPipelineLayout pipeline_layout_ = VK_NULL_HANDLE;
/// 描述符集列表
std::vector<VkDescriptorSet> descriptor_sets_;
};
// ================================================================================================
// 特化模式模板类(可选扩展)
// ================================================================================================
/**
* @brief Backdrop 模式控件基类模板
*
* 用于后效处理模式的控件。提供背景纹理采样能力。
*
* @tparam Bindings 满足 backdrop_bindings concept 的绑定类型
*/
template<typename Bindings>
requires backdrop_bindings<Bindings>
class backdrop_widget : public shader_widget<Bindings> {
public:
MIRAI_OBJECT_TYPE_INFO(backdrop_widget, shader_widget<Bindings>)
/**
* @brief 获取渲染模式
* @return 始终返回 backdrop
*/
[[nodiscard]] static constexpr shader_widget_mode mode() noexcept {
return shader_widget_mode::backdrop;
}
/**
* @brief 获取背景采样器绑定点
* @return 绑定点索引
*/
[[nodiscard]] static constexpr u32 backdrop_binding() noexcept {
return Bindings::BACKDROP_BINDING;
}
/**
* @brief 获取背景采样器所在描述符集
* @return 描述符集索引
*/
[[nodiscard]] static constexpr u32 backdrop_set() noexcept {
return Bindings::BACKDROP_SET;
}
};
/**
* @brief Procedural 模式控件基类模板
*
* 用于程序化渲染模式的控件。提供时间和坐标输入。
*
* @tparam Bindings 满足 procedural_bindings concept 的绑定类型
*/
template<typename Bindings>
requires procedural_bindings<Bindings>
class procedural_widget : public shader_widget<Bindings> {
public:
MIRAI_OBJECT_TYPE_INFO(procedural_widget, shader_widget<Bindings>)
/**
* @brief 获取渲染模式
* @return 始终返回 procedural
*/
[[nodiscard]] static constexpr shader_widget_mode mode() noexcept {
return shader_widget_mode::procedural;
}
/**
* @brief 获取帧数据绑定点
* @return 绑定点索引
*/
[[nodiscard]] static constexpr u32 frame_data_binding() noexcept {
return Bindings::FRAME_DATA_BINDING;
}
/**
* @brief 获取帧数据所在描述符集
* @return 描述符集索引
*/
[[nodiscard]] static constexpr u32 frame_data_set() noexcept {
return Bindings::FRAME_DATA_SET;
}
};
/**
* @brief Mask 模式控件基类模板
*
* 用于遮罩效果模式的控件。提供遮罩纹理采样能力。
*
* @tparam Bindings 满足 mask_bindings concept 的绑定类型
*/
template<typename Bindings>
requires mask_bindings<Bindings>
class mask_widget : public shader_widget<Bindings> {
public:
MIRAI_OBJECT_TYPE_INFO(mask_widget, shader_widget<Bindings>)
/**
* @brief 获取渲染模式
* @return 始终返回 mask
*/
[[nodiscard]] static constexpr shader_widget_mode mode() noexcept {
return shader_widget_mode::mask;
}
/**
* @brief 获取遮罩纹理绑定点
* @return 绑定点索引
*/
[[nodiscard]] static constexpr u32 mask_binding() noexcept {
return Bindings::MASK_BINDING;
}
/**
* @brief 获取遮罩纹理所在描述符集
* @return 描述符集索引
*/
[[nodiscard]] static constexpr u32 mask_set() noexcept {
return Bindings::MASK_SET;
}
};
/**
* @brief Mesh 模式控件基类模板
*
* 用于自定义网格渲染模式的控件。提供自定义顶点数据支持。
*
* @tparam Bindings 满足 mesh_bindings concept 的绑定类型
*/
template<typename Bindings>
requires mesh_bindings<Bindings>
class mesh_widget : public shader_widget<Bindings> {
public:
MIRAI_OBJECT_TYPE_INFO(mesh_widget, shader_widget<Bindings>)
/// 顶点类型别名
using vertex_type = typename Bindings::VertexType;
/**
* @brief 获取渲染模式
* @return 始终返回 mesh
*/
[[nodiscard]] static constexpr shader_widget_mode mode() noexcept {
return shader_widget_mode::mesh;
}
/**
* @brief 检查是否有自定义顶点输入
* @return 始终返回 true由 concept 保证)
*/
[[nodiscard]] static constexpr bool has_custom_vertex_input() noexcept {
return Bindings::HAS_CUSTOM_VERTEX_INPUT;
}
};
} // namespace mirai

View File

@@ -0,0 +1,139 @@
/**
* @file shader_widget_base.cpp
* @brief MIRAI 框架 shader_widget_base 类实现
* @author MIRAI Team
* @version 0.1.0
*/
#include "shader_widget_base.hpp"
#include "vulkan_device.hpp"
#include "allocator.hpp"
#include "buffer.hpp"
#include "descriptor_pool.hpp"
#include "assert.hpp"
namespace mirai {
// ================================================================================================
// 构造函数和析构函数
// ================================================================================================
shader_widget_base::shader_widget_base() = default;
shader_widget_base::~shader_widget_base() {
if (initialized_) {
destroy();
}
}
// ================================================================================================
// 生命周期方法
// ================================================================================================
void shader_widget_base::initialize(vulkan_device& device, gpu_allocator& allocator, descriptor_pool& pool) {
MIRAI_ASSERT(!initialized_, "shader_widget_base already initialized");
device_ = &device;
allocator_ = &allocator;
pool_ = &pool;
// 子类负责创建管线和描述符集
create_pipeline();
create_descriptor_sets();
initialized_ = true;
dirty_flags_ = widget_dirty_flags::all;
}
void shader_widget_base::destroy() {
if (!initialized_) {
return;
}
// 子类应该重写此方法以清理自己的资源
// 基类只负责清理引用
device_ = nullptr;
allocator_ = nullptr;
pool_ = nullptr;
initialized_ = false;
}
// ================================================================================================
// 渲染方法
// ================================================================================================
void shader_widget_base::update(command_buffer& cmd) {
if (!initialized_) {
return;
}
// 如果 uniform_buffer 标记为脏,则更新
if (has_flag(dirty_flags_, widget_dirty_flags::uniform_buffer)) {
update_uniform_buffer();
clear_dirty(widget_dirty_flags::uniform_buffer);
}
}
// ================================================================================================
// 状态管理
// ================================================================================================
void shader_widget_base::mark_dirty(widget_dirty_flags flags) {
dirty_flags_ |= flags;
}
void shader_widget_base::clear_dirty(widget_dirty_flags flags) {
dirty_flags_ = dirty_flags_ & (~flags);
}
bool shader_widget_base::is_dirty(widget_dirty_flags flag) const noexcept {
return has_flag(dirty_flags_, flag);
}
// ================================================================================================
// 区域设置
// ================================================================================================
void shader_widget_base::set_bounds(f32 x, f32 y, f32 width, f32 height) {
// 检查是否有变化
if (x_ != x || y_ != y || width_ != width || height_ != height) {
x_ = x;
y_ = y;
width_ = width;
height_ = height;
// 标记 uniform_buffer 为脏,因为边界变化通常需要更新 uniform 数据
mark_dirty(widget_dirty_flags::uniform_buffer);
}
}
// ================================================================================================
// 辅助方法
// ================================================================================================
std::unique_ptr<buffer> shader_widget_base::create_uniform_buffer(size_type size) {
MIRAI_ASSERT(allocator_ != nullptr, "allocator not set");
// 创建 buffer_create_info
buffer_create_info info;
info.size = static_cast<u64>(size);
info.usage = buffer_usage::uniform;
info.mem_usage = memory_usage::cpu_to_gpu;
info.persistent_mapped = true;
info.debug_name = "shader_widget_uniform";
// 注意buffer 类需要 shared_ptr<gpu_allocator>
// 但我们这里只有原始指针。在实际使用中,
// 可能需要通过其他方式获取 shared_ptr
// 或者修改 buffer 类接口。
//
// 这里返回 nullptr 作为占位实现,
// 实际的 buffer 创建需要在子类中处理,
// 因为子类可能有访问 shared_ptr<gpu_allocator> 的方式。
// TODO: 实现实际的 buffer 创建
// 当前返回空指针,子类需要重写此方法或使用其他方式创建 buffer
return nullptr;
}
} // namespace mirai

View File

@@ -0,0 +1,309 @@
/**
* @file shader_widget_base.hpp
* @brief MIRAI 框架 shader_widget 非模板基类
* @author MIRAI Team
* @version 0.1.0
*
* 本文件定义了 shader_widget 的非模板基类,包括:
* - 基本的渲染资源管理能力
* - 生命周期管理
* - 脏标记系统
* - 区域设置
*/
#pragma once
#include "object.hpp"
#include "widget_types.hpp"
#include "vulkan_types.hpp"
#include <memory>
namespace mirai {
// ================================================================================================
// 前向声明
// ================================================================================================
class vulkan_device;
class gpu_allocator;
class descriptor_pool;
class command_buffer;
class buffer;
class texture_view;
class sampler;
// ================================================================================================
// shader_widget_base 类
// ================================================================================================
/**
* @brief shader_widget 非模板基类
*
* 提供所有 shader_widget 的公共实现,避免模板代码膨胀。
* 子类通过继承此类获取基本的渲染资源管理能力。
*
* @note 此类不能直接实例化,必须通过派生类使用
*
* @example
* @code
* // shader_widget_base 作为所有着色器控件的基类
* // 通常不直接使用,而是通过模板化的 shader_widget<Bindings> 使用
* class my_widget : public shader_widget<my_bindings> {
* // 自定义实现
* };
* @endcode
*/
class shader_widget_base : public object {
public:
MIRAI_OBJECT_TYPE_INFO(shader_widget_base, object)
/**
* @brief 默认构造函数
*/
shader_widget_base();
/**
* @brief 虚析构函数
*/
~shader_widget_base() override;
// 禁止拷贝
shader_widget_base(const shader_widget_base&) = delete;
shader_widget_base& operator=(const shader_widget_base&) = delete;
// 禁止移动(因为只能通过智能指针管理)
shader_widget_base(shader_widget_base&&) = delete;
shader_widget_base& operator=(shader_widget_base&&) = delete;
// ============================================================================================
// 生命周期方法
// ============================================================================================
/**
* @brief 初始化 GPU 资源
*
* 初始化控件所需的 GPU 资源,包括管线、描述符集等。
* 必须在渲染前调用此方法。
*
* @param device Vulkan 设备
* @param allocator GPU 内存分配器
* @param pool 描述符池
*/
virtual void initialize(vulkan_device& device, gpu_allocator& allocator, descriptor_pool& pool);
/**
* @brief 销毁 GPU 资源
*
* 销毁控件持有的所有 GPU 资源。
* 在销毁控件或需要重新初始化时调用。
*/
virtual void destroy();
/**
* @brief 检查是否已初始化
* @return 如果已初始化返回 true
*/
[[nodiscard]] bool is_initialized() const noexcept { return initialized_; }
// ============================================================================================
// 渲染方法
// ============================================================================================
/**
* @brief 更新脏资源
*
* 检查并更新所有标记为脏的资源。
* 应在 draw() 之前调用。
*
* @param cmd 命令缓冲区(用于可能的数据传输)
*/
virtual void update(command_buffer& cmd);
/**
* @brief 记录绘制命令
*
* 将绘制命令记录到命令缓冲区。
* 子类必须实现此方法。
*
* @param cmd 命令缓冲区
*/
virtual void draw(command_buffer& cmd) = 0;
// ============================================================================================
// 状态管理
// ============================================================================================
/**
* @brief 标记为脏
*
* 标记指定的资源需要更新。
*
* @param flags 脏标记,默认为所有标记
*/
void mark_dirty(widget_dirty_flags flags = widget_dirty_flags::all);
/**
* @brief 清除脏标记
*
* 清除指定的脏标记。
*
* @param flags 要清除的标记
*/
void clear_dirty(widget_dirty_flags flags);
/**
* @brief 检查是否脏
*
* 检查是否有指定的脏标记。
*
* @param flag 要检查的标记
* @return 如果有指定标记返回 true
*/
[[nodiscard]] bool is_dirty(widget_dirty_flags flag) const noexcept;
/**
* @brief 获取当前脏标记
* @return 当前的脏标记集合
*/
[[nodiscard]] widget_dirty_flags dirty_flags() const noexcept { return dirty_flags_; }
// ============================================================================================
// 区域设置
// ============================================================================================
/**
* @brief 设置控件区域(相对于父控件)
*
* @param x X 坐标
* @param y Y 坐标
* @param width 宽度
* @param height 高度
*/
void set_bounds(f32 x, f32 y, f32 width, f32 height);
/**
* @brief 获取 X 坐标
* @return X 坐标
*/
[[nodiscard]] f32 x() const noexcept { return x_; }
/**
* @brief 获取 Y 坐标
* @return Y 坐标
*/
[[nodiscard]] f32 y() const noexcept { return y_; }
/**
* @brief 获取宽度
* @return 宽度
*/
[[nodiscard]] f32 width() const noexcept { return width_; }
/**
* @brief 获取高度
* @return 高度
*/
[[nodiscard]] f32 height() const noexcept { return height_; }
protected:
// ============================================================================================
// 子类实现的钩子方法
// ============================================================================================
/**
* @brief 创建管线
*
* 子类必须实现此方法来创建渲染管线。
* 在 initialize() 中调用。
*/
virtual void create_pipeline() = 0;
/**
* @brief 创建描述符集
*
* 子类必须实现此方法来创建描述符集。
* 在 initialize() 中调用。
*/
virtual void create_descriptor_sets() = 0;
/**
* @brief 更新 Uniform Buffer
*
* 子类必须实现此方法来更新 Uniform Buffer 数据。
* 在 update() 中当 uniform_buffer 标记为脏时调用。
*/
virtual void update_uniform_buffer() = 0;
// ============================================================================================
// 辅助方法
// ============================================================================================
/**
* @brief 创建 Uniform Buffer
*
* 使用 GPU 分配器创建指定大小的 Uniform Buffer。
*
* @param size 缓冲区大小(字节)
* @return 创建的缓冲区
*/
[[nodiscard]] std::unique_ptr<buffer> create_uniform_buffer(size_type size);
/**
* @brief 获取设备引用
* @return Vulkan 设备引用
*/
[[nodiscard]] vulkan_device& device() { return *device_; }
/**
* @brief 获取设备引用const 版本)
* @return Vulkan 设备常量引用
*/
[[nodiscard]] const vulkan_device& device() const { return *device_; }
/**
* @brief 获取分配器引用
* @return GPU 内存分配器引用
*/
[[nodiscard]] gpu_allocator& allocator() { return *allocator_; }
/**
* @brief 获取描述符池引用
* @return 描述符池引用
*/
[[nodiscard]] descriptor_pool& pool() { return *pool_; }
protected:
// ============================================================================================
// 成员变量
// ============================================================================================
/// 设备引用(不持有所有权)
vulkan_device* device_ = nullptr;
/// 分配器引用(不持有所有权)
gpu_allocator* allocator_ = nullptr;
/// 描述符池引用(不持有所有权)
descriptor_pool* pool_ = nullptr;
/// 是否已初始化
bool initialized_ = false;
/// 脏标记
widget_dirty_flags dirty_flags_ = widget_dirty_flags::all;
/// X 坐标
f32 x_ = 0.0f;
/// Y 坐标
f32 y_ = 0.0f;
/// 宽度
f32 width_ = 0.0f;
/// 高度
f32 height_ = 0.0f;
};
} // namespace mirai

220
src/widget/widget_types.hpp Normal file
View File

@@ -0,0 +1,220 @@
/**
* @file widget_types.hpp
* @brief MIRAI 框架 Widget 模块基础类型定义
* @author MIRAI Team
* @version 0.1.0
*
* 本文件定义了 Widget 模块使用的基础类型,包括:
* - 着色器绑定 Concepts
* - 渲染模式枚举
* - 脏标记枚举
*/
#pragma once
#include "types/types.hpp"
#include <concepts>
#include <span>
#include <cstdint>
namespace mirai {
// ================================================================================================
// 前向声明
// ================================================================================================
class vulkan_device;
class gpu_allocator;
class descriptor_pool;
class command_buffer;
class texture_view;
class sampler;
// ================================================================================================
// 着色器绑定 Concepts
// ================================================================================================
/**
* @brief 基础绑定检查
*
* 所有绑定类型必须满足此 concept提供基本的着色器信息
*/
template<typename T>
concept shader_bindings = requires {
/// 是否有顶点着色器
{ T::HAS_VERTEX_SHADER } -> std::convertible_to<bool>;
/// 是否有片段着色器
{ T::HAS_FRAGMENT_SHADER } -> std::convertible_to<bool>;
/// 获取顶点着色器 SPIR-V 数据
{ T::get_vertex_spirv() } -> std::same_as<std::span<const u32>>;
/// 获取片段着色器 SPIR-V 数据
{ T::get_fragment_spirv() } -> std::same_as<std::span<const u32>>;
};
/**
* @brief Backdrop 模式绑定检查
*
* 用于后效处理模式的绑定类型必须满足此 concept
*/
template<typename T>
concept backdrop_bindings = shader_bindings<T> && requires {
/// 是否有背景采样器
{ T::HAS_BACKDROP_SAMPLER } -> std::convertible_to<bool>;
/// 背景纹理绑定点
{ T::BACKDROP_BINDING } -> std::convertible_to<u32>;
/// 背景纹理所在描述符集
{ T::BACKDROP_SET } -> std::convertible_to<u32>;
};
/**
* @brief Procedural 模式绑定检查
*
* 用于程序化渲染模式的绑定类型必须满足此 concept
*/
template<typename T>
concept procedural_bindings = shader_bindings<T> && requires {
/// 是否有帧数据
{ T::HAS_FRAME_DATA } -> std::convertible_to<bool>;
/// 帧数据绑定点
{ T::FRAME_DATA_BINDING } -> std::convertible_to<u32>;
/// 帧数据所在描述符集
{ T::FRAME_DATA_SET } -> std::convertible_to<u32>;
};
/**
* @brief Mask 模式绑定检查
*
* 用于遮罩效果模式的绑定类型必须满足此 concept
*/
template<typename T>
concept mask_bindings = shader_bindings<T> && requires {
/// 是否有遮罩采样器
{ T::HAS_MASK_SAMPLER } -> std::convertible_to<bool>;
/// 遮罩纹理绑定点
{ T::MASK_BINDING } -> std::convertible_to<u32>;
/// 遮罩纹理所在描述符集
{ T::MASK_SET } -> std::convertible_to<u32>;
};
/**
* @brief Mesh 模式绑定检查
*
* 用于自定义网格渲染模式的绑定类型必须满足此 concept
*/
template<typename T>
concept mesh_bindings = shader_bindings<T> && requires {
/// 是否有自定义顶点输入
{ T::HAS_CUSTOM_VERTEX_INPUT } -> std::convertible_to<bool>;
/// 顶点类型
typename T::VertexType;
};
// ================================================================================================
// 渲染模式枚举
// ================================================================================================
/**
* @brief 着色器控件渲染模式
*
* 定义了着色器控件支持的四种渲染模式
*/
enum class shader_widget_mode : u8 {
/// 后效处理模式:采样背景纹理进行处理
backdrop,
/// 程序化渲染模式:基于时间和坐标的程序化生成
procedural,
/// 遮罩模式:使用遮罩纹理进行裁剪或混合
mask,
/// 自定义网格模式:使用自定义顶点数据渲染
mesh
};
// ================================================================================================
// 脏标记枚举
// ================================================================================================
/**
* @brief 控件脏标记
*
* 用于标记哪些资源需要更新
*/
enum class widget_dirty_flags : u32 {
/// 无脏标记
none = 0,
/// Uniform Buffer 需要更新
uniform_buffer = 1 << 0,
/// 描述符集需要更新
descriptor_set = 1 << 1,
/// 管线需要重建
pipeline = 1 << 2,
/// 顶点缓冲区需要更新
vertex_buffer = 1 << 3,
/// 所有标记
all = 0xFFFFFFFF
};
// ================================================================================================
// 位运算支持
// ================================================================================================
/**
* @brief 脏标记按位或运算
* @param a 第一个操作数
* @param b 第二个操作数
* @return 运算结果
*/
constexpr widget_dirty_flags operator|(widget_dirty_flags a, widget_dirty_flags b) {
return static_cast<widget_dirty_flags>(static_cast<u32>(a) | static_cast<u32>(b));
}
/**
* @brief 脏标记按位与运算
* @param a 第一个操作数
* @param b 第二个操作数
* @return 运算结果
*/
constexpr widget_dirty_flags operator&(widget_dirty_flags a, widget_dirty_flags b) {
return static_cast<widget_dirty_flags>(static_cast<u32>(a) & static_cast<u32>(b));
}
/**
* @brief 脏标记按位或赋值运算
* @param a 被赋值的操作数
* @param b 第二个操作数
* @return 赋值后的引用
*/
constexpr widget_dirty_flags& operator|=(widget_dirty_flags& a, widget_dirty_flags b) {
return a = a | b;
}
/**
* @brief 脏标记按位与赋值运算
* @param a 被赋值的操作数
* @param b 第二个操作数
* @return 赋值后的引用
*/
constexpr widget_dirty_flags& operator&=(widget_dirty_flags& a, widget_dirty_flags b) {
return a = a & b;
}
/**
* @brief 脏标记按位取反运算
* @param a 操作数
* @return 取反结果
*/
constexpr widget_dirty_flags operator~(widget_dirty_flags a) {
return static_cast<widget_dirty_flags>(~static_cast<u32>(a));
}
/**
* @brief 检查是否包含指定标记
* @param flags 标记集合
* @param flag 要检查的标记
* @return 如果包含返回 true
*/
[[nodiscard]] constexpr bool has_flag(widget_dirty_flags flags, widget_dirty_flags flag) {
return (static_cast<u32>(flags) & static_cast<u32>(flag)) != 0;
}
} // namespace mirai

View File

@@ -0,0 +1,745 @@
#!/usr/bin/env python3
"""
MIRAI 着色器绑定代码生成器
读取 SPIR-V 和反射 JSON生成 C++ 绑定代码。
Usage:
python generate_shader_bindings.py \
--dir ./shader_intermediate \
--output generated/shader_name_bindings.hpp \
--name ShaderName
"""
import argparse
import json
import struct
import re
from pathlib import Path
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Any
from datetime import datetime
from jinja2 import Environment, FileSystemLoader, select_autoescape
# ============================================================================
# 数据结构
# ============================================================================
@dataclass
class UniformMember:
"""Uniform Buffer 成员"""
name: str
type: str
offset: int
size: int
array_size: int = 1
@dataclass
class UniformBuffer:
"""Uniform Buffer 描述"""
name: str
set: int
binding: int
size: int
members: List[UniformMember] = field(default_factory=list)
@dataclass
class PushConstant:
"""Push Constant 描述"""
name: str
size: int
stage_flags: int = 0 # Vulkan 着色器阶段标志位掩码
offset: int = 0 # Push constant 偏移量(通常为 0
members: List[UniformMember] = field(default_factory=list)
@dataclass
class SamplerBinding:
"""采样器绑定描述"""
name: str
set: int
binding: int
dimension: str = "2D"
@dataclass
class ShaderStage:
"""着色器阶段"""
stage: str # vertex, fragment, compute
entry_point: str
spirv: bytes = b''
@dataclass
class ShaderReflection:
"""着色器反射数据"""
name: str
stages: List[ShaderStage] = field(default_factory=list)
uniform_buffers: List[UniformBuffer] = field(default_factory=list)
push_constants: List[PushConstant] = field(default_factory=list)
samplers: List[SamplerBinding] = field(default_factory=list)
# 每个阶段特有的资源
stage_uniform_buffers: Dict[str, List[UniformBuffer]] = field(default_factory=dict)
stage_samplers: Dict[str, List[SamplerBinding]] = field(default_factory=dict)
stage_push_constants: Dict[str, List[PushConstant]] = field(default_factory=dict)
# ============================================================================
# 类型映射
# ============================================================================
# Slang/HLSL 类型到 C++ 类型的映射
SLANG_TO_CPP_TYPE = {
"float": "float",
"float2": "Eigen::Vector2f",
"float3": "Eigen::Vector3f",
"float4": "Eigen::Vector4f",
"int": "int32_t",
"int2": "Eigen::Vector2i",
"int3": "Eigen::Vector3i",
"int4": "Eigen::Vector4i",
"uint": "uint32_t",
"uint2": "Eigen::Matrix<uint32_t, 2, 1>",
"uint3": "Eigen::Matrix<uint32_t, 3, 1>",
"uint4": "Eigen::Matrix<uint32_t, 4, 1>",
"float2x2": "Eigen::Matrix2f",
"float3x3": "Eigen::Matrix3f",
"float4x4": "Eigen::Matrix4f",
"bool": "uint32_t", # GLSL bool 是 4 字节
"matrix": "Eigen::Matrix4f",
}
# STD140 对齐规则
STD140_ALIGNMENT = {
"float": 4,
"float2": 8,
"float3": 16,
"float4": 16,
"int": 4,
"int2": 8,
"int3": 16,
"int4": 16,
"uint": 4,
"uint2": 8,
"uint3": 16,
"uint4": 16,
"float2x2": 16,
"float3x3": 16,
"float4x4": 16,
"bool": 4,
"matrix": 16,
}
# ============================================================================
# 解析器
# ============================================================================
def parse_reflection_json(json_path: Path) -> ShaderReflection:
"""解析 Slang 生成的反射 JSON"""
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
name = json_path.stem.replace('.reflect', '')
# 从文件名检测阶段
stage = detect_stage_from_filename(json_path.name)
if stage is None:
# 尝试从反射数据中获取阶段
stage = data.get('stage', None)
# 解析 Uniform Buffers
uniform_buffers = []
for ub in data.get('uniformBuffers', []):
members = [
UniformMember(
name=m['name'],
type=m['type'],
offset=m['offset'],
size=m['size'],
array_size=m.get('arraySize', 1)
)
for m in ub.get('members', [])
]
uniform_buffers.append(UniformBuffer(
name=ub['name'],
set=ub['set'],
binding=ub['binding'],
size=ub['size'],
members=members
))
# 解析 Push Constants
push_constants = []
for pc in data.get('pushConstants', []):
members = [
UniformMember(
name=m['name'],
type=m['type'],
offset=m['offset'],
size=m['size']
)
for m in pc.get('members', [])
]
push_constants.append(PushConstant(
name=pc['name'],
size=pc['size'],
stage_flags=pc.get('stageFlags', 0),
offset=pc.get('offset', 0),
members=members
))
# 解析 Samplers
samplers = []
for s in data.get('samplers', []):
samplers.append(SamplerBinding(
name=s['name'],
set=s['set'],
binding=s['binding'],
dimension=s.get('dimension', '2D')
))
# 按阶段存储的资源
stage_uniform_buffers = {}
stage_samplers = {}
stage_push_constants = {}
if stage:
stage_key = get_stage_suffix(stage).lower() # 使用小写作为字典键
stage_uniform_buffers[stage_key] = uniform_buffers
stage_samplers[stage_key] = samplers
stage_push_constants[stage_key] = push_constants
return ShaderReflection(
name=name,
stages=[],
uniform_buffers=uniform_buffers,
push_constants=push_constants,
samplers=samplers,
stage_uniform_buffers=stage_uniform_buffers,
stage_samplers=stage_samplers,
stage_push_constants=stage_push_constants
)
def merge_reflections(reflections: List[ShaderReflection]) -> ShaderReflection:
"""合并多个反射数据(取并集)"""
if not reflections:
return ShaderReflection(name="merged")
if len(reflections) == 1:
return reflections[0]
# 使用第一个反射的名称作为合并后的名称
merged_name = reflections[0].name
# 合并 uniform buffers按 set/binding 去重)
uniform_buffers_dict: Dict[tuple, UniformBuffer] = {}
for ref in reflections:
for ub in ref.uniform_buffers:
key = (ub.set, ub.binding)
if key not in uniform_buffers_dict:
uniform_buffers_dict[key] = ub
# 合并 push constants按名称去重
push_constants_dict: Dict[str, PushConstant] = {}
for ref in reflections:
for pc in ref.push_constants:
if pc.name not in push_constants_dict:
push_constants_dict[pc.name] = pc
# 合并 samplers按 set/binding 去重)
samplers_dict: Dict[tuple, SamplerBinding] = {}
for ref in reflections:
for s in ref.samplers:
key = (s.set, s.binding)
if key not in samplers_dict:
samplers_dict[key] = s
# 合并每个阶段特有的资源
stage_uniform_buffers: Dict[str, List[UniformBuffer]] = {}
stage_samplers: Dict[str, List[SamplerBinding]] = {}
stage_push_constants: Dict[str, List[PushConstant]] = {}
for ref in reflections:
for stage_key, ubs in ref.stage_uniform_buffers.items():
if stage_key not in stage_uniform_buffers:
stage_uniform_buffers[stage_key] = []
stage_uniform_buffers[stage_key].extend(ubs)
for stage_key, samps in ref.stage_samplers.items():
if stage_key not in stage_samplers:
stage_samplers[stage_key] = []
stage_samplers[stage_key].extend(samps)
for stage_key, pcs in ref.stage_push_constants.items():
if stage_key not in stage_push_constants:
stage_push_constants[stage_key] = []
stage_push_constants[stage_key].extend(pcs)
return ShaderReflection(
name=merged_name,
stages=[],
uniform_buffers=list(uniform_buffers_dict.values()),
push_constants=list(push_constants_dict.values()),
samplers=list(samplers_dict.values()),
stage_uniform_buffers=stage_uniform_buffers,
stage_samplers=stage_samplers,
stage_push_constants=stage_push_constants
)
def load_spirv(spirv_path: Path) -> bytes:
"""加载 SPIR-V 二进制文件"""
with open(spirv_path, 'rb') as f:
return f.read()
def detect_stage_from_filename(filename: str) -> Optional[str]:
"""从文件名检测着色器阶段"""
filename_lower = filename.lower()
if '.vert.' in filename_lower or filename_lower.endswith('.vert'):
return 'vertex'
elif '.frag.' in filename_lower or filename_lower.endswith('.frag'):
return 'fragment'
elif '.comp.' in filename_lower or filename_lower.endswith('.comp'):
return 'compute'
elif '.geom.' in filename_lower or filename_lower.endswith('.geom'):
return 'geometry'
elif '.tesc.' in filename_lower or filename_lower.endswith('.tesc'):
return 'tessellation_control'
elif '.tese.' in filename_lower or filename_lower.endswith('.tese'):
return 'tessellation_evaluation'
return None
# ============================================================================
# 辅助函数
# ============================================================================
def to_snake_case(name: str) -> str:
"""转换为 snake_case"""
# 如果已经是全小写下划线连接,不做转换
if name.islower() and '_' in name:
return name
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
snake = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
# 处理连续大写字母的情况(如 "ABCDef" -> "abc_def"
snake = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1_\2', snake)
snake = re.sub(r'([a-z])([A-Z])', r'\1_\2', snake).lower()
return snake
def to_camel_case(name: str) -> str:
"""转换为 CamelCase首字母大写的驼峰式"""
parts = name.split('_')
return ''.join(part.title() for part in parts if part)
def to_upper_snake_case(name: str) -> str:
"""转换为 UPPER_SNAKE_CASE"""
return to_snake_case(name).upper()
def get_stage_suffix(stage: str) -> str:
"""获取着色器阶段后缀"""
return {
'vertex': 'VERT',
'fragment': 'FRAG',
'compute': 'COMP',
'geometry': 'GEOM',
'tessellation_control': 'TESC',
'tessellation_evaluation': 'TESE',
}.get(stage, stage.upper())
def spirv_to_hex_lines(spirv: bytes) -> List[str]:
"""将 SPIR-V 转换为十六进制行"""
if len(spirv) == 0:
return []
word_count = len(spirv) // 4
words = struct.unpack(f'<{word_count}I', spirv[:word_count * 4])
lines = []
for i in range(0, len(words), 8):
chunk = words[i:i+8]
hex_values = ', '.join(f'0x{w:08x}' for w in chunk)
lines.append(f"{hex_values},")
return lines
def prepare_template_context(
shader_name: str,
reflection: ShaderReflection,
stages: Dict[str, bytes]
) -> Dict[str, Any]:
"""准备 Jinja2 模板上下文"""
upper_snake_name = to_upper_snake_case(shader_name)
guard = f"MIRAI_GENERATED_SHADER_{upper_snake_name}_HPP"
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 准备 SPIR-V 数据
stages_data = {}
for stage, spirv in stages.items():
stage_suffix = get_stage_suffix(stage)
word_count = len(spirv) // 4 if spirv else 0
stages_data[stage_suffix] = {
'word_count': word_count,
'hex_lines': spirv_to_hex_lines(spirv),
}
# 准备 Uniform Buffer 数据
uniform_buffers_data = []
for ub in reflection.uniform_buffers:
struct_name = ub.name # 保持原始名称
members_data = []
calculated_size = 0
for member in ub.members:
cpp_type = SLANG_TO_CPP_TYPE.get(member.type, member.type)
alignment = STD140_ALIGNMENT.get(member.type, 16)
if member.array_size > 1:
cpp_type = f"std::array<{cpp_type}, {member.array_size}>"
members_data.append({
'name': member.name, # 保持原始名称
'type_name': member.type,
'cpp_type': cpp_type,
'alignment': alignment,
'offset': member.offset,
'size': member.size,
'array_size': member.array_size,
})
# 计算最大偏移量+大小作为 fallback size
member_end = member.offset + member.size
if member_end > calculated_size:
calculated_size = member_end
# 使用反射数据中的 size如果为 0 则使用计算的值
final_size = ub.size if ub.size > 0 else calculated_size
# 还需要考虑 16 字节对齐
final_size = (final_size + 15) & ~15
uniform_buffers_data.append({
'name': ub.name,
'struct_name': struct_name,
'snake_name': ub.name,
'set': ub.set,
'binding': ub.binding,
'size': final_size,
'members': members_data,
})
# 准备 Push Constant 数据
push_constants_data = []
for pc in reflection.push_constants:
struct_name = pc.name # 保持原始名称
members_data = []
for member in pc.members:
cpp_type = SLANG_TO_CPP_TYPE.get(member.type, member.type)
members_data.append({
'name': member.name, # 保持原始名称
'type_name': member.type,
'cpp_type': cpp_type,
'offset': member.offset,
'size': member.size,
})
push_constants_data.append({
'name': pc.name,
'struct_name': struct_name,
'snake_name': pc.name,
'size': pc.size,
'stage_flags': pc.stage_flags,
'offset': pc.offset,
'members': members_data,
})
# 准备采样器数据
samplers_data = []
for s in reflection.samplers:
samplers_data.append({
'name': s.name,
'snake_name': s.name, # 保持原始名称
'set': s.set,
'binding': s.binding,
'dimension': s.dimension,
})
# 准备绑定常量数据
bindings_data = []
for ub in reflection.uniform_buffers:
bindings_data.append({
'const_name': to_upper_snake_case(ub.name),
'set': ub.set,
'binding': ub.binding,
})
for s in reflection.samplers:
bindings_data.append({
'const_name': to_upper_snake_case(s.name),
'set': s.set,
'binding': s.binding,
})
# 准备每个阶段特有的资源数据
stage_resources_data = {}
for stage_key, ubs in reflection.stage_uniform_buffers.items():
stage_ubs_data = []
for ub in ubs:
struct_name = ub.name # 保持原始名称
members_data = []
calculated_size = 0
for member in ub.members:
cpp_type = SLANG_TO_CPP_TYPE.get(member.type, member.type)
alignment = STD140_ALIGNMENT.get(member.type, 16)
if member.array_size > 1:
cpp_type = f"std::array<{cpp_type}, {member.array_size}>"
members_data.append({
'name': member.name, # 保持原始名称
'type_name': member.type,
'cpp_type': cpp_type,
'alignment': alignment,
'offset': member.offset,
'size': member.size,
})
member_end = member.offset + member.size
if member_end > calculated_size:
calculated_size = member_end
final_size = ub.size if ub.size > 0 else calculated_size
final_size = (final_size + 15) & ~15
stage_ubs_data.append({
'name': ub.name,
'struct_name': struct_name,
'snake_name': ub.name,
'set': ub.set,
'binding': ub.binding,
'size': final_size,
'members': members_data,
})
stage_resources_data[f"{stage_key}_uniform_buffers"] = stage_ubs_data
for stage_key, pcs in reflection.stage_push_constants.items():
stage_pcs_data = []
for pc in pcs:
struct_name = pc.name # 保持原始名称
members_data = []
for member in pc.members:
cpp_type = SLANG_TO_CPP_TYPE.get(member.type, member.type)
members_data.append({
'name': member.name, # 保持原始名称
'type_name': member.type,
'cpp_type': cpp_type,
'offset': member.offset,
'size': member.size,
})
stage_pcs_data.append({
'name': pc.name,
'struct_name': struct_name,
'snake_name': pc.name,
'size': pc.size,
'stage_flags': pc.stage_flags,
'offset': pc.offset,
'members': members_data,
})
stage_resources_data[f"{stage_key}_push_constants"] = stage_pcs_data
for stage_key, samps in reflection.stage_samplers.items():
stage_samps_data = []
for s in samps:
stage_samps_data.append({
'name': s.name,
'snake_name': s.name, # 保持原始名称
'set': s.set,
'binding': s.binding,
'dimension': s.dimension,
})
stage_resources_data[f"{stage_key}_samplers"] = stage_samps_data
return {
'shader_name': shader_name,
'snake_name': shader_name,
'upper_snake_name': upper_snake_name,
'guard': guard,
'timestamp': timestamp,
'stages': stages_data,
'uniform_buffers': uniform_buffers_data,
'push_constants': push_constants_data,
'samplers': samplers_data,
'bindings': bindings_data,
'uniform_buffer_count': len(reflection.uniform_buffers),
'stage_resources': stage_resources_data,
'sampler_count': len(reflection.samplers),
# 静态检查所需的元数据
'has_uniform_buffers': len(reflection.uniform_buffers) > 0,
'has_push_constants': len(reflection.push_constants) > 0,
'has_samplers': len(reflection.samplers) > 0,
'has_vertex_shader': 'VERT' in stages_data or 'vertex' in stages,
'has_fragment_shader': 'FRAG' in stages_data or 'fragment' in stages,
}
# ============================================================================
# 代码生成器
# ============================================================================
def create_jinja_env(template_dir: Path) -> Environment:
"""创建 Jinja2 环境"""
return Environment(
loader=FileSystemLoader(template_dir),
autoescape=select_autoescape(['html', 'xml']),
trim_blocks=True,
lstrip_blocks=True,
keep_trailing_newline=True,
)
def generate_header(
shader_name: str,
reflection: ShaderReflection,
stages: Dict[str, bytes],
template_dir: Path,
template_name: str = 'shader_bindings.hpp.j2'
) -> str:
"""使用 Jinja2 生成 C++ 头文件"""
env = create_jinja_env(template_dir)
template = env.get_template(template_name)
context = prepare_template_context(shader_name, reflection, stages)
return template.render(context)
# ============================================================================
# 主函数
# ============================================================================
def main():
parser = argparse.ArgumentParser(
description='MIRAI 着色器绑定代码生成器',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
python generate_shader_bindings.py \\
--dir ./shader_intermediate/basic \\
--output generated/basic_bindings.hpp \\
--name basic
"""
)
parser.add_argument(
'--dir', '-d',
required=True,
help='包含 SPIR-V 和反射文件的目录'
)
parser.add_argument(
'--output', '-o',
required=True,
help='输出的 C++ 头文件路径'
)
parser.add_argument(
'--name', '-n',
required=True,
help='着色器名称 (用于生成类名和变量名)'
)
parser.add_argument(
'--template-dir', '-t',
default=None,
help='Jinja2 模板目录 (默认: 脚本所在目录的 templates 子目录)'
)
parser.add_argument(
'--verbose', '-v',
action='store_true',
help='输出详细信息'
)
args = parser.parse_args()
# 确定模板
template_name = 'widget_bindings.hpp.j2'
# 确定模板目录
if args.template_dir:
template_dir = Path(args.template_dir)
else:
template_dir = Path(__file__).parent / 'templates'
if not template_dir.exists():
print(f"Error: Template directory not found: {template_dir}")
return 1
# 从目录中查找文件
shader_dir = Path(args.dir)
if not shader_dir.exists():
print(f"Info: Shader directory not found: {shader_dir}. No entry points, skipping.")
return 0
# 查找所有 spv 和 reflect.json 文件
spirv_files = sorted(shader_dir.glob("*.spv"))
reflect_files = sorted(shader_dir.glob("*.reflect.json"))
if args.verbose:
print(f"Found {len(spirv_files)} SPIR-V files")
print(f"Found {len(reflect_files)} reflection files")
# 如果没有反射文件,跳过生成
if not reflect_files:
print(f"Info: No reflection files found in {shader_dir}. Skipping binding generation.")
return 0
# 解析反射数据
all_reflections = []
for reflection_path in reflect_files:
if args.verbose:
print(f"Parsing reflection: {reflection_path}")
reflection = parse_reflection_json(reflection_path)
all_reflections.append(reflection)
# 合并多个反射数据(取并集)
reflection = merge_reflections(all_reflections)
# 加载 SPIR-V 文件
stages: Dict[str, bytes] = {}
for spirv_path in spirv_files:
stage = detect_stage_from_filename(spirv_path.name)
if stage is None:
if args.verbose:
print(f"Warning: Cannot detect shader stage from filename: {spirv_path.name}")
stage = spirv_path.stem.split('.')[-1] if '.' in spirv_path.stem else 'unknown'
if args.verbose:
print(f"Loading SPIR-V ({stage}): {spirv_path}")
stages[stage] = load_spirv(spirv_path)
# 生成头文件
if args.verbose:
print(f"Generating header: {args.output}")
print(f"Using templates from: {template_dir}")
header_content = generate_header(args.name, reflection, stages, template_dir, template_name)
# 确保输出目录存在
output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
# 写入文件
with open(output_path, 'w', encoding='utf-8') as f:
f.write(header_content)
print(f"Generated: {output_path}")
if args.verbose:
print(f" - Stages: {list(stages.keys())}")
print(f" - Uniform Buffers: {len(reflection.uniform_buffers)}")
print(f" - Samplers: {len(reflection.samplers)}")
print(f" - Push Constants: {len(reflection.push_constants)}")
return 0
if __name__ == '__main__':
exit(main())

1
tools/requirements.txt Normal file
View File

@@ -0,0 +1 @@
jinja2>=3.0

View File

@@ -0,0 +1,32 @@
# tools/shader_compile/CMakeLists.txt
# MIRAI 着色器编译器工具
# 用于从 SPIR-V 提取反射信息并生成绑定代码
cmake_minimum_required(VERSION 3.20)
# 查找 spirv-cross 用于反射
find_package(spirv_cross_core CONFIG REQUIRED)
find_package(spirv_cross_glsl CONFIG REQUIRED)
find_package(spirv_cross_reflect CONFIG REQUIRED)
# 查找 nlohmann_json 用于反射 JSON 输出
find_package(nlohmann_json CONFIG REQUIRED)
add_executable(mirai_shader_compile
main.cpp
compiler.cpp
)
target_compile_features(mirai_shader_compile PRIVATE cxx_std_20)
target_link_libraries(mirai_shader_compile PRIVATE
spirv-cross-core
spirv-cross-glsl
spirv-cross-reflect
nlohmann_json::nlohmann_json
)
# 安装到 bin 目录
install(TARGETS mirai_shader_compile
RUNTIME DESTINATION bin
)

View File

@@ -0,0 +1,522 @@
// tools/shader_compile/compiler.cpp
// MIRAI 着色器反射工具实现
// 使用 spirv-cross 从 SPIR-V 提取反射信息
#include "compiler.hpp"
#include <spirv_cross/spirv_cross.hpp>
#include <spirv_cross/spirv_glsl.hpp>
#include <nlohmann/json.hpp>
#include <fstream>
#include <iostream>
#include <sstream>
namespace mirai::tools {
// ============================================================================
// Helper Functions
// ============================================================================
std::optional<shader_stage> parse_shader_stage(const std::string& str) {
if (str == "vertex" || str == "vert" || str == "vs") {
return shader_stage::vertex;
}
if (str == "fragment" || str == "frag" || str == "pixel" || str == "fs" || str == "ps") {
return shader_stage::fragment;
}
if (str == "compute" || str == "comp" || str == "cs") {
return shader_stage::compute;
}
if (str == "geometry" || str == "geom" || str == "gs") {
return shader_stage::geometry;
}
if (str == "tesscontrol" || str == "tesc" || str == "hull" || str == "hs") {
return shader_stage::tessellation_control;
}
if (str == "tesseval" || str == "tese" || str == "domain" || str == "ds") {
return shader_stage::tessellation_evaluation;
}
return std::nullopt;
}
const char* shader_stage_to_string(shader_stage stage) {
switch (stage) {
case shader_stage::vertex: return "vertex";
case shader_stage::fragment: return "fragment";
case shader_stage::compute: return "compute";
case shader_stage::geometry: return "geometry";
case shader_stage::tessellation_control: return "tesscontrol";
case shader_stage::tessellation_evaluation: return "tesseval";
case shader_stage::unknown: return "unknown";
}
return "unknown";
}
shader_stage stage_from_extension(const std::string& ext) {
std::string ext_lower = ext;
for (auto& c : ext_lower) {
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
// 处理 .spv 后缀,如 shader.vert.spv
if (ext_lower.find(".vert") != std::string::npos) {
return shader_stage::vertex;
}
if (ext_lower.find(".frag") != std::string::npos) {
return shader_stage::fragment;
}
if (ext_lower.find(".comp") != std::string::npos) {
return shader_stage::compute;
}
if (ext_lower.find(".geom") != std::string::npos) {
return shader_stage::geometry;
}
if (ext_lower.find(".tesc") != std::string::npos) {
return shader_stage::tessellation_control;
}
if (ext_lower.find(".tese") != std::string::npos) {
return shader_stage::tessellation_evaluation;
}
return shader_stage::unknown;
}
// ============================================================================
// SPIRV-Cross Type Helpers
// ============================================================================
static std::string spirv_type_to_string(const spirv_cross::Compiler& compiler,
const spirv_cross::SPIRType& type) {
switch (type.basetype) {
case spirv_cross::SPIRType::Float:
if (type.columns > 1) {
// Matrix
return "float" + std::to_string(type.columns) + "x" + std::to_string(type.vecsize);
} else if (type.vecsize > 1) {
// Vector
return "float" + std::to_string(type.vecsize);
}
return "float";
case spirv_cross::SPIRType::Int:
if (type.vecsize > 1) {
return "int" + std::to_string(type.vecsize);
}
return "int";
case spirv_cross::SPIRType::UInt:
if (type.vecsize > 1) {
return "uint" + std::to_string(type.vecsize);
}
return "uint";
case spirv_cross::SPIRType::Boolean:
if (type.vecsize > 1) {
return "bool" + std::to_string(type.vecsize);
}
return "bool";
case spirv_cross::SPIRType::Double:
if (type.columns > 1) {
return "double" + std::to_string(type.columns) + "x" + std::to_string(type.vecsize);
} else if (type.vecsize > 1) {
return "double" + std::to_string(type.vecsize);
}
return "double";
case spirv_cross::SPIRType::Struct:
return compiler.get_name(type.self);
case spirv_cross::SPIRType::SampledImage:
case spirv_cross::SPIRType::Image:
return "sampler";
default:
return "unknown";
}
}
static std::string image_dim_to_string(spv::Dim dim) {
switch (dim) {
case spv::Dim1D: return "1D";
case spv::Dim2D: return "2D";
case spv::Dim3D: return "3D";
case spv::DimCube: return "Cube";
case spv::DimBuffer: return "Buffer";
default: return "2D";
}
}
// ============================================================================
// spirv_reflector Implementation
// ============================================================================
reflection_result spirv_reflector::reflect_file(const std::filesystem::path& path) {
reflection_result result;
// 读取 SPIR-V 文件
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file) {
result.error_message = "Failed to open file: " + path.string();
return result;
}
auto file_size = file.tellg();
file.seekg(0, std::ios::beg);
if (file_size % sizeof(uint32_t) != 0) {
result.error_message = "Invalid SPIR-V file size: " + path.string();
return result;
}
std::vector<uint32_t> spirv(file_size / sizeof(uint32_t));
file.read(reinterpret_cast<char*>(spirv.data()), file_size);
if (!file) {
result.error_message = "Failed to read file: " + path.string();
return result;
}
auto res = reflect_spirv(spirv, path.filename().string());
res.source_filename = path.filename().string();
res.spirv_data = std::move(spirv);
return res;
}
reflection_result spirv_reflector::reflect_spirv(
const std::vector<uint32_t>& spirv,
const std::string& filename
) {
reflection_result result;
try {
spirv_cross::Compiler compiler(spirv);
spirv_cross::ShaderResources resources = compiler.get_shader_resources();
// 获取入口点
auto entry_points = compiler.get_entry_points_and_stages();
if (!entry_points.empty()) {
result.reflection.entry_point = entry_points[0].name;
// 从 SPIR-V 执行模型推断阶段
switch (entry_points[0].execution_model) {
case spv::ExecutionModelVertex:
result.reflection.stage = shader_stage::vertex;
break;
case spv::ExecutionModelFragment:
result.reflection.stage = shader_stage::fragment;
break;
case spv::ExecutionModelGLCompute:
result.reflection.stage = shader_stage::compute;
break;
case spv::ExecutionModelGeometry:
result.reflection.stage = shader_stage::geometry;
break;
case spv::ExecutionModelTessellationControl:
result.reflection.stage = shader_stage::tessellation_control;
break;
case spv::ExecutionModelTessellationEvaluation:
result.reflection.stage = shader_stage::tessellation_evaluation;
break;
default:
result.reflection.stage = shader_stage::unknown;
break;
}
} else if (!filename.empty()) {
// 从文件名推断阶段
result.reflection.stage = stage_from_extension(filename);
}
// 处理 Uniform Buffers
for (const auto& ub : resources.uniform_buffers) {
uniform_buffer_info info;
info.name = compiler.get_name(ub.id);
if (info.name.empty()) {
info.name = compiler.get_fallback_name(ub.id);
}
info.set = compiler.get_decoration(ub.id, spv::DecorationDescriptorSet);
info.binding = compiler.get_decoration(ub.id, spv::DecorationBinding);
const auto& type = compiler.get_type(ub.base_type_id);
info.size = static_cast<uint32_t>(compiler.get_declared_struct_size(type));
// 获取成员信息
for (uint32_t i = 0; i < type.member_types.size(); ++i) {
uniform_member member;
member.name = compiler.get_member_name(type.self, i);
const auto& member_type = compiler.get_type(type.member_types[i]);
member.type = spirv_type_to_string(compiler, member_type);
member.offset = compiler.type_struct_member_offset(type, i);
member.size = static_cast<uint32_t>(compiler.get_declared_struct_member_size(type, i));
if (!member_type.array.empty()) {
member.array_size = member_type.array[0];
}
info.members.push_back(member);
}
result.reflection.uniform_buffers.push_back(info);
}
// 处理 Push Constants
for (const auto& pc : resources.push_constant_buffers) {
push_constant_info info;
info.name = compiler.get_name(pc.id);
if (info.name.empty()) {
info.name = "PushConstants";
}
const auto& type = compiler.get_type(pc.base_type_id);
info.size = static_cast<uint32_t>(compiler.get_declared_struct_size(type));
// 获取成员信息
for (uint32_t i = 0; i < type.member_types.size(); ++i) {
uniform_member member;
member.name = compiler.get_member_name(type.self, i);
const auto& member_type = compiler.get_type(type.member_types[i]);
member.type = spirv_type_to_string(compiler, member_type);
member.offset = compiler.type_struct_member_offset(type, i);
member.size = static_cast<uint32_t>(compiler.get_declared_struct_member_size(type, i));
if (!member_type.array.empty()) {
member.array_size = member_type.array[0];
}
info.members.push_back(member);
}
result.reflection.push_constants.push_back(info);
}
// 处理采样器/纹理
for (const auto& img : resources.sampled_images) {
sampler_info info;
info.name = compiler.get_name(img.id);
if (info.name.empty()) {
info.name = compiler.get_fallback_name(img.id);
}
info.set = compiler.get_decoration(img.id, spv::DecorationDescriptorSet);
info.binding = compiler.get_decoration(img.id, spv::DecorationBinding);
const auto& type = compiler.get_type(img.type_id);
info.dimension = image_dim_to_string(type.image.dim);
info.is_array = type.image.arrayed;
result.reflection.samplers.push_back(info);
}
// 处理独立纹理
for (const auto& img : resources.separate_images) {
sampler_info info;
info.name = compiler.get_name(img.id);
if (info.name.empty()) {
info.name = compiler.get_fallback_name(img.id);
}
info.set = compiler.get_decoration(img.id, spv::DecorationDescriptorSet);
info.binding = compiler.get_decoration(img.id, spv::DecorationBinding);
const auto& type = compiler.get_type(img.type_id);
info.dimension = image_dim_to_string(type.image.dim);
info.is_array = type.image.arrayed;
result.reflection.samplers.push_back(info);
}
// 处理顶点输入(仅顶点着色器)
if (result.reflection.stage == shader_stage::vertex) {
for (const auto& input : resources.stage_inputs) {
vertex_attribute_info attr;
attr.name = compiler.get_name(input.id);
if (attr.name.empty()) {
attr.name = compiler.get_fallback_name(input.id);
}
attr.location = compiler.get_decoration(input.id, spv::DecorationLocation);
const auto& type = compiler.get_type(input.type_id);
attr.type = spirv_type_to_string(compiler, type);
result.reflection.vertex_inputs.push_back(attr);
}
}
// 处理片段输出(仅片段着色器)
if (result.reflection.stage == shader_stage::fragment) {
for (const auto& output : resources.stage_outputs) {
vertex_attribute_info attr;
attr.name = compiler.get_name(output.id);
if (attr.name.empty()) {
attr.name = compiler.get_fallback_name(output.id);
}
attr.location = compiler.get_decoration(output.id, spv::DecorationLocation);
const auto& type = compiler.get_type(output.type_id);
attr.type = spirv_type_to_string(compiler, type);
result.reflection.fragment_outputs.push_back(attr);
}
}
result.success = true;
result.reflection_json = reflection_to_json(result.reflection);
} catch (const spirv_cross::CompilerError& e) {
result.error_message = std::string("SPIRV-Cross error: ") + e.what();
} catch (const std::exception& e) {
result.error_message = std::string("Error: ") + e.what();
}
return result;
}
std::vector<reflection_result> spirv_reflector::reflect_directory(
const std::filesystem::path& dir
) {
std::vector<reflection_result> results;
if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) {
return results;
}
for (const auto& entry : std::filesystem::directory_iterator(dir)) {
if (!entry.is_regular_file()) {
continue;
}
auto ext = entry.path().extension().string();
if (ext == ".spv") {
auto result = reflect_file(entry.path());
results.push_back(result);
}
}
return results;
}
// ============================================================================
// JSON Serialization
// ============================================================================
std::string reflection_to_json(const shader_reflection& reflection) {
nlohmann::json root;
root["entryPoint"] = reflection.entry_point;
root["stage"] = shader_stage_to_string(reflection.stage);
// Uniform Buffers
nlohmann::json uniform_buffers = nlohmann::json::array();
for (const auto& ub : reflection.uniform_buffers) {
nlohmann::json ub_json;
ub_json["name"] = ub.name;
ub_json["set"] = ub.set;
ub_json["binding"] = ub.binding;
ub_json["size"] = ub.size;
nlohmann::json members = nlohmann::json::array();
for (const auto& member : ub.members) {
nlohmann::json member_json;
member_json["name"] = member.name;
member_json["type"] = member.type;
member_json["offset"] = member.offset;
member_json["size"] = member.size;
if (member.array_size > 0) {
member_json["arraySize"] = member.array_size;
}
members.push_back(member_json);
}
ub_json["members"] = members;
uniform_buffers.push_back(ub_json);
}
root["uniformBuffers"] = uniform_buffers;
// Push Constants
nlohmann::json push_constants = nlohmann::json::array();
for (const auto& pc : reflection.push_constants) {
nlohmann::json pc_json;
pc_json["name"] = pc.name;
pc_json["size"] = pc.size;
nlohmann::json members = nlohmann::json::array();
for (const auto& member : pc.members) {
nlohmann::json member_json;
member_json["name"] = member.name;
member_json["type"] = member.type;
member_json["offset"] = member.offset;
member_json["size"] = member.size;
if (member.array_size > 0) {
member_json["arraySize"] = member.array_size;
}
members.push_back(member_json);
}
pc_json["members"] = members;
push_constants.push_back(pc_json);
}
root["pushConstants"] = push_constants;
// Samplers
nlohmann::json samplers = nlohmann::json::array();
for (const auto& sampler : reflection.samplers) {
nlohmann::json sampler_json;
sampler_json["name"] = sampler.name;
sampler_json["set"] = sampler.set;
sampler_json["binding"] = sampler.binding;
sampler_json["dimension"] = sampler.dimension;
if (sampler.is_array) {
sampler_json["isArray"] = true;
}
samplers.push_back(sampler_json);
}
root["samplers"] = samplers;
// Vertex Inputs
if (!reflection.vertex_inputs.empty()) {
nlohmann::json inputs = nlohmann::json::array();
for (const auto& input : reflection.vertex_inputs) {
nlohmann::json input_json;
input_json["name"] = input.name;
input_json["location"] = input.location;
input_json["type"] = input.type;
inputs.push_back(input_json);
}
root["vertexInputs"] = inputs;
}
// Fragment Outputs
if (!reflection.fragment_outputs.empty()) {
nlohmann::json outputs = nlohmann::json::array();
for (const auto& output : reflection.fragment_outputs) {
nlohmann::json output_json;
output_json["name"] = output.name;
output_json["location"] = output.location;
output_json["type"] = output.type;
outputs.push_back(output_json);
}
root["fragmentOutputs"] = outputs;
}
return root.dump(2);
}
std::string reflections_to_json(const std::vector<shader_reflection>& reflections) {
nlohmann::json root;
root["shaders"] = nlohmann::json::array();
for (const auto& reflection : reflections) {
root["shaders"].push_back(nlohmann::json::parse(reflection_to_json(reflection)));
}
return root.dump(2);
}
} // namespace mirai::tools

View File

@@ -0,0 +1,175 @@
// tools/shader_compile/compiler.hpp
// MIRAI 着色器反射工具
// 使用 spirv-cross 从 SPIR-V 提取反射信息
#pragma once
#include <cstdint>
#include <filesystem>
#include <optional>
#include <string>
#include <vector>
namespace mirai::tools {
/**
* @brief 着色器阶段
*/
enum class shader_stage {
vertex,
fragment,
compute,
geometry,
tessellation_control,
tessellation_evaluation,
unknown
};
/**
* @brief 将字符串转换为着色器阶段
*/
[[nodiscard]] std::optional<shader_stage> parse_shader_stage(const std::string& str);
/**
* @brief 将着色器阶段转换为字符串
*/
[[nodiscard]] const char* shader_stage_to_string(shader_stage stage);
/**
* @brief 从文件扩展名推断着色器阶段
*/
[[nodiscard]] shader_stage stage_from_extension(const std::string& ext);
/**
* @brief Uniform Buffer 成员信息
*/
struct uniform_member {
std::string name;
std::string type;
uint32_t offset{0};
uint32_t size{0};
uint32_t array_size{0}; // 0 表示非数组
};
/**
* @brief Uniform Buffer 信息
*/
struct uniform_buffer_info {
std::string name;
uint32_t set{0};
uint32_t binding{0};
uint32_t size{0};
std::vector<uniform_member> members;
};
/**
* @brief Push Constant 信息
*/
struct push_constant_info {
std::string name;
uint32_t size{0};
std::vector<uniform_member> members;
};
/**
* @brief 采样器/纹理信息
*/
struct sampler_info {
std::string name;
uint32_t set{0};
uint32_t binding{0};
std::string dimension; // "1D", "2D", "3D", "Cube"
bool is_array{false};
};
/**
* @brief 顶点输入属性信息
*/
struct vertex_attribute_info {
std::string name;
uint32_t location{0};
std::string type;
};
/**
* @brief 着色器反射数据
*/
struct shader_reflection {
std::string entry_point{"main"};
shader_stage stage{shader_stage::unknown};
std::vector<uniform_buffer_info> uniform_buffers;
std::vector<push_constant_info> push_constants;
std::vector<sampler_info> samplers;
std::vector<vertex_attribute_info> vertex_inputs;
std::vector<vertex_attribute_info> fragment_outputs;
};
/**
* @brief SPIR-V 反射结果
*/
struct reflection_result {
bool success{false};
shader_reflection reflection;
std::string reflection_json;
std::string error_message;
std::string source_filename; // 源文件名
std::vector<uint32_t> spirv_data; // SPIR-V 字节码
};
/**
* @brief SPIR-V 反射器
*
* 使用 spirv-cross 从 SPIR-V 字节码提取反射信息
*/
class spirv_reflector {
public:
spirv_reflector() = default;
~spirv_reflector() = default;
// 禁止拷贝
spirv_reflector(const spirv_reflector&) = delete;
spirv_reflector& operator=(const spirv_reflector&) = delete;
// 允许移动
spirv_reflector(spirv_reflector&&) noexcept = default;
spirv_reflector& operator=(spirv_reflector&&) noexcept = default;
/**
* @brief 从 SPIR-V 文件提取反射信息
* @param path SPIR-V 文件路径
* @return 反射结果
*/
[[nodiscard]] reflection_result reflect_file(const std::filesystem::path& path);
/**
* @brief 从 SPIR-V 字节码提取反射信息
* @param spirv SPIR-V 字节码
* @param filename 文件名(用于错误报告和阶段推断)
* @return 反射结果
*/
[[nodiscard]] reflection_result reflect_spirv(
const std::vector<uint32_t>& spirv,
const std::string& filename = ""
);
/**
* @brief 从目录中的所有 SPIR-V 文件提取反射信息
* @param dir 目录路径
* @return 反射结果列表
*/
[[nodiscard]] std::vector<reflection_result> reflect_directory(
const std::filesystem::path& dir
);
};
/**
* @brief 将反射数据转换为 JSON 字符串
*/
[[nodiscard]] std::string reflection_to_json(const shader_reflection& reflection);
/**
* @brief 将多个反射数据合并为一个 JSON 字符串
*/
[[nodiscard]] std::string reflections_to_json(const std::vector<shader_reflection>& reflections);
} // namespace mirai::tools

View File

@@ -0,0 +1,368 @@
// tools/shader_compile/main.cpp
// MIRAI 着色器反射工具命令行入口
// 从 SPIR-V 文件提取反射信息并生成绑定代码
#include "compiler.hpp"
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
namespace {
void print_usage(const char* program_name) {
std::cout << R"(
MIRAI Shader Reflection Tool
Usage: )" << program_name << R"( [options]
Options:
--dir <path> Directory containing SPIR-V files
--file <path> Single SPIR-V file to process
--output <path> Output file path (header or JSON)
--name <name> Shader group name (for header generation)
--json Output JSON reflection data
--header Output C++ header with bindings (default)
-h, --help Show this help
-v, --version Show version info
Examples:
)" << program_name << R"( --dir ./shaders --output bindings.hpp --name my_shader
)" << program_name << R"( --file shader.vert.spv --output shader.json --json
)" << program_name << R"( --dir ./compiled --output reflection.json --json
Output Formats:
Header (.hpp): C++ header with embedded SPIR-V and reflection data
JSON (.json): Structured reflection data for external tools
)";
}
void print_version() {
std::cout << "MIRAI Shader Reflection Tool v1.0.0\n";
std::cout << "Based on SPIRV-Cross\n";
}
struct command_line_args {
std::filesystem::path input_dir;
std::filesystem::path input_file;
std::filesystem::path output_path;
std::string name;
bool output_json = false;
bool output_header = true;
bool show_help = false;
bool show_version = false;
};
bool parse_args(int argc, char* argv[], command_line_args& args) {
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "-h" || arg == "--help") {
args.show_help = true;
return true;
}
if (arg == "-v" || arg == "--version") {
args.show_version = true;
return true;
}
if (arg == "--dir") {
if (++i >= argc) {
std::cerr << "Error: --dir requires an argument\n";
return false;
}
args.input_dir = argv[i];
}
else if (arg == "--file") {
if (++i >= argc) {
std::cerr << "Error: --file requires an argument\n";
return false;
}
args.input_file = argv[i];
}
else if (arg == "--output" || arg == "-o") {
if (++i >= argc) {
std::cerr << "Error: --output requires an argument\n";
return false;
}
args.output_path = argv[i];
}
else if (arg == "--name") {
if (++i >= argc) {
std::cerr << "Error: --name requires an argument\n";
return false;
}
args.name = argv[i];
}
else if (arg == "--json") {
args.output_json = true;
args.output_header = false;
}
else if (arg == "--header") {
args.output_header = true;
args.output_json = false;
}
else {
std::cerr << "Error: Unknown option: " << arg << "\n";
return false;
}
}
return true;
}
bool write_text(const std::filesystem::path& path, const std::string& text) {
std::ofstream file(path);
if (!file) {
std::cerr << "Error: Failed to open output file: " << path << "\n";
return false;
}
file << text;
return file.good();
}
std::vector<uint8_t> read_binary_file(const std::filesystem::path& path) {
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file) {
return {};
}
auto size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(size);
file.read(reinterpret_cast<char*>(data.data()), size);
return data;
}
std::string generate_header(
const std::string& name,
const std::vector<mirai::tools::reflection_result>& results
) {
std::ostringstream ss;
// Header guard
std::string guard_name = name;
for (auto& c : guard_name) {
c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
}
ss << "// Auto-generated shader bindings for: " << name << "\n";
ss << "// DO NOT EDIT - Generated by mirai_shader_compile\n\n";
ss << "#pragma once\n\n";
ss << "#include <array>\n";
ss << "#include <cstdint>\n";
ss << "#include <span>\n";
ss << "#include <string_view>\n\n";
ss << "namespace mirai::shaders {\n\n";
ss << "namespace " << name << " {\n\n";
// Generate SPIR-V data for each shader
for (const auto& result : results) {
if (!result.success) {
continue;
}
const auto& reflection = result.reflection;
std::string stage_name = mirai::tools::shader_stage_to_string(reflection.stage);
ss << "// " << stage_name << " shader\n";
ss << "namespace " << stage_name << " {\n\n";
ss << "constexpr std::string_view entry_point = \"" << reflection.entry_point << "\";\n\n";
// Embed SPIR-V bytecode
if (!result.spirv_data.empty()) {
ss << "// SPIR-V bytecode (" << result.spirv_data.size() << " words, "
<< (result.spirv_data.size() * 4) << " bytes)\n";
ss << "inline constexpr std::array<uint32_t, " << result.spirv_data.size() << "> spirv_code = {\n";
// Write SPIR-V data in rows of 8 values
for (size_t i = 0; i < result.spirv_data.size(); ++i) {
if (i % 8 == 0) {
ss << " ";
}
ss << "0x" << std::hex << std::setfill('0') << std::setw(8) << result.spirv_data[i];
if (i + 1 < result.spirv_data.size()) {
ss << ",";
}
if ((i + 1) % 8 == 0 || i + 1 == result.spirv_data.size()) {
ss << "\n";
} else {
ss << " ";
}
}
ss << std::dec; // Reset to decimal
ss << "};\n\n";
// Provide a span view for easy access
ss << "inline constexpr std::span<const uint32_t> spirv() {\n";
ss << " return spirv_code;\n";
ss << "}\n\n";
// Provide byte view
ss << "inline const uint8_t* spirv_bytes() {\n";
ss << " return reinterpret_cast<const uint8_t*>(spirv_code.data());\n";
ss << "}\n\n";
ss << "inline constexpr size_t spirv_size() {\n";
ss << " return spirv_code.size() * sizeof(uint32_t);\n";
ss << "}\n\n";
}
// Uniform buffer info
if (!reflection.uniform_buffers.empty()) {
ss << "// Uniform Buffers\n";
for (const auto& ub : reflection.uniform_buffers) {
ss << "struct " << ub.name << " {\n";
ss << " static constexpr uint32_t set = " << ub.set << ";\n";
ss << " static constexpr uint32_t binding = " << ub.binding << ";\n";
ss << " static constexpr uint32_t size = " << ub.size << ";\n";
ss << "};\n\n";
}
}
// Push constant info
if (!reflection.push_constants.empty()) {
ss << "// Push Constants\n";
for (const auto& pc : reflection.push_constants) {
ss << "struct " << pc.name << " {\n";
ss << " static constexpr uint32_t size = " << pc.size << ";\n";
ss << "};\n\n";
}
}
// Sampler info
if (!reflection.samplers.empty()) {
ss << "// Samplers\n";
for (const auto& sampler : reflection.samplers) {
ss << "struct " << sampler.name << "_info {\n";
ss << " static constexpr uint32_t set = " << sampler.set << ";\n";
ss << " static constexpr uint32_t binding = " << sampler.binding << ";\n";
ss << " static constexpr std::string_view dimension = \"" << sampler.dimension << "\";\n";
ss << "};\n\n";
}
}
ss << "} // namespace " << stage_name << "\n\n";
}
// Reflection JSON
ss << "// Combined reflection data\n";
ss << "constexpr std::string_view reflection_json = R\"JSON(\n";
std::vector<mirai::tools::shader_reflection> reflections;
for (const auto& result : results) {
if (result.success) {
reflections.push_back(result.reflection);
}
}
ss << mirai::tools::reflections_to_json(reflections);
ss << "\n)JSON\";\n\n";
ss << "} // namespace " << name << "\n\n";
ss << "} // namespace mirai::shaders\n";
return ss.str();
}
} // anonymous namespace
int main(int argc, char* argv[]) {
command_line_args args;
if (!parse_args(argc, argv, args)) {
return 1;
}
if (args.show_help) {
print_usage(argv[0]);
return 0;
}
if (args.show_version) {
print_version();
return 0;
}
if (args.input_dir.empty() && args.input_file.empty()) {
std::cerr << "Error: No input specified. Use --dir or --file.\n";
print_usage(argv[0]);
return 1;
}
if (args.output_path.empty()) {
std::cerr << "Error: No output path specified. Use --output.\n";
return 1;
}
// Create reflector
mirai::tools::spirv_reflector reflector;
std::vector<mirai::tools::reflection_result> results;
// Process input
if (!args.input_dir.empty()) {
results = reflector.reflect_directory(args.input_dir);
} else if (!args.input_file.empty()) {
auto result = reflector.reflect_file(args.input_file);
results.push_back(result);
}
if (results.empty()) {
std::cout << "Warning: No SPIR-V files found to process.\n";
return 0;
}
// Check for errors
int error_count = 0;
for (const auto& result : results) {
if (!result.success) {
std::cerr << "Error: " << result.error_message << "\n";
error_count++;
}
}
// Generate output
std::string output;
if (args.output_json) {
std::vector<mirai::tools::shader_reflection> reflections;
for (const auto& result : results) {
if (result.success) {
reflections.push_back(result.reflection);
}
}
output = mirai::tools::reflections_to_json(reflections);
} else {
// Header output
if (args.name.empty()) {
// Try to derive name from output path
args.name = args.output_path.stem().string();
// Remove _bindings suffix if present
if (args.name.ends_with("_bindings")) {
args.name = args.name.substr(0, args.name.length() - 9);
}
}
output = generate_header(args.name, results);
}
// Write output
if (!write_text(args.output_path, output)) {
return 1;
}
std::cout << "Generated: " << args.output_path << "\n";
int success_count = static_cast<int>(results.size()) - error_count;
std::cout << "Processed " << success_count << " shader(s)";
if (error_count > 0) {
std::cout << " (" << error_count << " error(s))";
}
std::cout << "\n";
return error_count > 0 ? 1 : 0;
}

View File

@@ -0,0 +1,325 @@
/**
* @file {{ snake_name }}_bindings.hpp
* @brief 自动生成的着色器绑定 - 请勿手动修改
* @generated by generate_shader_bindings.py at {{ timestamp }}
*/
#pragma once
#include <array>
#include <cstdint>
#include <span>
#include <string_view>
#include <Eigen/Core>
#include <shader_types.hpp>
namespace mirai::generated {
// ============================================================================
// SPIR-V 字节码
// ============================================================================
{% for stage_name, spirv_data in stages.items() %}
inline constexpr std::array<uint32_t, {{ spirv_data.word_count }}> {{ upper_snake_name }}_{{ stage_name | upper }}_SPIRV = {
{% for line in spirv_data.hex_lines %}
{{ line }}
{% endfor %}
};
{% endfor %}
{% if uniform_buffers %}
// ============================================================================
// Uniform 结构体 (合并后的全局视图)
// ============================================================================
{% for ub in uniform_buffers %}
/**
* @brief {{ ub.struct_name }} Uniform Buffer
* @details Set: {{ ub.set }}, Binding: {{ ub.binding }}, Size: {{ ub.size }} bytes
*/
struct alignas(16) {{ ub.struct_name }} {
{% for member in ub.members %}
alignas({{ member.alignment }}) {{ member.cpp_type }} {{ member.name }};
{% endfor %}
};
static_assert(sizeof({{ ub.struct_name }}) == {{ ub.size }}, "{{ ub.struct_name }} size mismatch");
{% endfor %}
{% endif %}
{% if stage_resources %}
// ============================================================================
// 按阶段划分的 Uniform 结构体
// ============================================================================
{% for stage_key in ['VERT', 'FRAG', 'COMP', 'GEOM', 'TESC', 'TESE'] %}
{% set key = stage_key.lower() + '_uniform_buffers' %}
{% set stage_prefix = stage_key %}
{% set ubs = stage_resources.get(key, []) %}
{% if ubs %}
// -------------------------- {{ stage_prefix }} 阶段 --------------------------
{% for ub in ubs %}
/**
* @brief {{ ub.name }} ({{ stage_prefix }})
* @details Set: {{ ub.set }}, Binding: {{ ub.binding }}, Size: {{ ub.size }} bytes
*/
struct alignas(16) {{ ub.name }}_{{ stage_prefix }} {
{% for member in ub.members %}
alignas({{ member.alignment }}) {{ member.cpp_type }} {{ member.name }};
{% endfor %}
};
static_assert(sizeof({{ ub.name }}_{{ stage_prefix }}) == {{ ub.size }}, "{{ ub.name }}_{{ stage_prefix }} size mismatch");
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
{% if push_constants %}
// ============================================================================
// Push Constant 结构体 (合并后的全局视图)
// ============================================================================
{% for pc in push_constants %}
/**
* @brief {{ pc.struct_name }} Push Constant
* @details Size: {{ pc.size }} bytes
*/
struct {{ pc.struct_name }} {
{% for member in pc.members %}
{{ member.cpp_type }} {{ member.name }};
{% endfor %}
};
static_assert(sizeof({{ pc.struct_name }}) <= 128, "Push constant exceeds 128 bytes");
{% endfor %}
{% endif %}
{% if stage_resources %}
// ============================================================================
// 按阶段划分的 Push Constant 结构体
// ============================================================================
{% for stage_key in ['VERT', 'FRAG', 'COMP', 'GEOM', 'TESC', 'TESE'] %}
{% set key = stage_key.lower() + '_push_constants' %}
{% set stage_prefix = stage_key %}
{% set pcs = stage_resources.get(key, []) %}
{% if pcs %}
// -------------------------- {{ stage_prefix }} 阶段 --------------------------
{% for pc in pcs %}
/**
* @brief {{ pc.name }} ({{ stage_prefix }})
* @details Size: {{ pc.size }} bytes
*/
struct {{ pc.name }}_{{ stage_prefix }} {
{% for member in pc.members %}
{{ member.cpp_type }} {{ member.name }};
{% endfor %}
};
static_assert(sizeof({{ pc.name }}_{{ stage_prefix }}) <= 128, "Push constant exceeds 128 bytes");
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
// ============================================================================
// 绑定辅助类
// ============================================================================
/**
* @brief {{ snake_name }} 着色器绑定辅助类
*/
class {{ snake_name }}_bindings {
public:
{% if bindings %}
/// 描述符集和绑定常量
{% for binding in bindings %}
static constexpr uint32_t SET_{{ binding.const_name }} = {{ binding.set }};
static constexpr uint32_t BINDING_{{ binding.const_name }} = {{ binding.binding }};
{% endfor %}
{% endif %}
/// Uniform Buffer 数量
static constexpr uint32_t UNIFORM_BUFFER_COUNT = {{ uniform_buffer_count }};
/// 采样器数量
static constexpr uint32_t SAMPLER_COUNT = {{ sampler_count }};
{% if 'VERT' in stages %}
/// 获取顶点着色器 SPIR-V
[[nodiscard]] static std::span<const uint32_t> get_vertex_spirv() noexcept {
return {{ upper_snake_name }}_VERT_SPIRV;
}
{% endif %}
{% if 'FRAG' in stages %}
/// 获取片段着色器 SPIR-V
[[nodiscard]] static std::span<const uint32_t> get_fragment_spirv() noexcept {
return {{ upper_snake_name }}_FRAG_SPIRV;
}
{% endif %}
{% if 'COMP' in stages %}
/// 获取计算着色器 SPIR-V
[[nodiscard]] static std::span<const uint32_t> get_compute_spirv() noexcept {
return {{ upper_snake_name }}_COMP_SPIRV;
}
{% endif %}
/// 获取着色器名称
[[nodiscard]] static constexpr std::string_view get_name() noexcept {
return "{{ shader_name }}";
}
};
// ============================================================================
// 静态反射元数据(使用 shader_types.hpp 中定义的结构)
// ============================================================================
{% if uniform_buffers %}
{% for ub in uniform_buffers %}
{% if ub.members %}
/// {{ ub.name }} Uniform Block 成员元数据
{% set members_array = upper_snake_name + '_' + ub.snake_name | upper + '_MEMBERS' %}
inline constexpr std::array<static_uniform_member, {{ ub.members | length }}> {{ members_array }} = {
{% for member in ub.members %}
static_uniform_member{"{{ member.name }}", "{{ member.type_name }}", {{ member.offset }}, {{ member.size }}}{{ "," if not loop.last else "" }}
{% endfor %}
};
{% endif %}
{% endfor %}
/// Uniform Blocks 元数据
{% set ub_array_name = upper_snake_name + '_UNIFORM_BLOCKS' %}
inline constexpr std::array<static_uniform_block_info, {{ uniform_buffers | length }}> {{ ub_array_name }} = {
{% for ub in uniform_buffers %}
static_uniform_block_info{
"{{ ub.name }}",
{{ ub.set }},
{{ ub.binding }},
{{ ub.size }},
{% if ub.members %}
{{ upper_snake_name }}_{{ ub.snake_name | upper }}_MEMBERS
{% else %}
{}
{% endif %}
}{{ "," if not loop.last else "" }}
{% endfor %}
};
{% endif %}
{% if samplers %}
/// 纹理/采样器元数据
{% set textures_array_name = upper_snake_name + '_TEXTURES' %}
inline constexpr std::array<static_texture_info, {{ samplers | length }}> {{ textures_array_name }} = {
{% for s in samplers %}
static_texture_info{"{{ s.name }}", {{ s.set }}, {{ s.binding }}, "{{ s.dimension }}"}{{ "," if not loop.last else "" }}
{% endfor %}
};
{% endif %}
{% if push_constants %}
{% for pc in push_constants %}
{% if pc.members %}
{% set members_array = upper_snake_name + '_' + pc.snake_name | upper + '_PC_MEMBERS' %}
/// {{ pc.name }} Push Constant 成员元数据
inline constexpr std::array<static_push_constant_member, {{ pc.members | length }}> {{ members_array }} = {
{% for member in pc.members %}
static_push_constant_member{"{{ member.name }}", "{{ member.type_name }}", {{ member.offset }}, {{ member.size }}}{{ "," if not loop.last else "" }}
{% endfor %}
};
{% endif %}
{% endfor %}
/// Push Constants 元数据
{% set pc_array_name = upper_snake_name + '_PUSH_CONSTANTS' %}
inline constexpr std::array<static_push_constant_info, {{ push_constants | length }}> {{ pc_array_name }} = {
{% for pc in push_constants %}
static_push_constant_info{
"{{ pc.name }}",
{{ pc.stage_flags }},
{{ pc.offset }},
{{ pc.size }},
{% if pc.members %}
{{ upper_snake_name }}_{{ pc.snake_name | upper }}_PC_MEMBERS
{% else %}
{}
{% endif %}
}{{ "," if not loop.last else "" }}
{% endfor %}
};
{% endif %}
{% if vertex_inputs %}
/// 顶点输入属性元数据
{% set vi_array_name = upper_snake_name + '_VERTEX_INPUTS' %}
inline constexpr std::array<static_vertex_input_info, {{ vertex_inputs | length }}> {{ vi_array_name }} = {
{% for vi in vertex_inputs %}
static_vertex_input_info{"{{ vi.name }}", {{ vi.location }}, "{{ vi.type_name }}", {{ vi.format }}, {{ vi.offset }}}{{ "," if not loop.last else "" }}
{% endfor %}
};
{% endif %}
/// 完整的静态着色器反射数据
inline constexpr static_shader_reflection {{ upper_snake_name }}_REFLECTION = {
"{{ shader_name }}",
{% if uniform_buffers %}
{{ upper_snake_name }}_UNIFORM_BLOCKS,
{% else %}
{},
{% endif %}
{% if samplers %}
{{ upper_snake_name }}_TEXTURES,
{% else %}
{},
{% endif %}
{% if push_constants %}
{{ upper_snake_name }}_PUSH_CONSTANTS,
{% else %}
{},
{% endif %}
{% if vertex_inputs %}
{{ upper_snake_name }}_VERTEX_INPUTS
{% else %}
{}
{% endif %}
};
/**
* @brief {{ snake_name }} 着色器反射辅助类
*/
struct {{ snake_name }}_reflection {
static constexpr std::string_view NAME = "{{ shader_name }}";
/// 获取完整的反射数据
[[nodiscard]] static constexpr const static_shader_reflection& get() noexcept {
return {{ upper_snake_name }}_REFLECTION;
}
{% if uniform_buffers %}
/// 获取 Uniform Blocks
[[nodiscard]] static constexpr std::span<const static_uniform_block_info> uniform_blocks() noexcept {
return {{ upper_snake_name }}_UNIFORM_BLOCKS;
}
{% endif %}
{% if samplers %}
/// 获取纹理/采样器
[[nodiscard]] static constexpr std::span<const static_texture_info> textures() noexcept {
return {{ upper_snake_name }}_TEXTURES;
}
{% endif %}
{% if push_constants %}
/// 获取 Push Constants
[[nodiscard]] static constexpr std::span<const static_push_constant_info> push_constants() noexcept {
return {{ upper_snake_name }}_PUSH_CONSTANTS;
}
{% endif %}
{% if vertex_inputs %}
/// 获取顶点输入属性
[[nodiscard]] static constexpr std::span<const static_vertex_input_info> vertex_inputs() noexcept {
return {{ upper_snake_name }}_VERTEX_INPUTS;
}
{% endif %}
};
} // namespace mirai::generated

View File

@@ -0,0 +1,377 @@
/**
* @file {{ snake_name }}_bindings.hpp
* @brief {{ shader_name }} 着色器绑定模板类 - 自动生成
* @generated by generate_shader_bindings.py at {{ timestamp }}
*
* 本文件包含 {{ shader_name }} 着色器的所有绑定数据。
* 控件使用此模板类时应静态检查是否满足资源需求。
*
* 使用方法:
* 1. 继承 shader_widget 并指定此绑定类
* 2. 静态检查所需资源是否存在
* 3. 使用 bindings_type:: 访问绑定常量和数据
*/
#pragma once
#include <cstdint>
#include <array>
#include <span>
#include <type_traits>
{% if has_vertex_shader %}
#include <vulkan/vulkan.hpp>
{% endif %}
namespace mirai::generated {
// ============================================================================
// {{ shader_name }} 着色器绑定模板类
// ============================================================================
/**
* @brief {{ shader_name }} 绑定模板类
*
* 包含着色器的所有反射数据:
* - Uniform Buffer 数据结构
* - Push Constant 数据结构
* - 绑定常量
* - SPIR-V 字节码
*
* 控件使用此模板时应静态检查所需资源:
* - 检查 has_samplers 是否满足纹理输入需求
* - 检查 has_uniform_buffers 是否满足 Uniform Buffer 需求
* - 检查 has_push_constants 是否满足推送常量需求
*/
struct {{ snake_name }}_bindings {
// ========================================================================
// 静态特征检查
// ========================================================================
/// 是否有顶点着色器
static constexpr bool HAS_VERTEX_SHADER = {{ 'true' if has_vertex_shader else 'false' }};
/// 是否有片段着色器
static constexpr bool HAS_FRAGMENT_SHADER = {{ 'true' if has_fragment_shader else 'false' }};
/// 是否有 Uniform Buffers
static constexpr bool HAS_UNIFORM_BUFFERS = {{ 'true' if has_uniform_buffers else 'false' }};
/// 是否有 Push Constants
static constexpr bool HAS_PUSH_CONSTANTS = {{ 'true' if has_push_constants else 'false' }};
/// 是否有 Samplers (纹理输入)
static constexpr bool HAS_SAMPLERS = {{ 'true' if has_samplers else 'false' }};
/// Uniform Buffer 数量
static constexpr uint32_t UNIFORM_BUFFER_COUNT = {{ uniform_buffer_count }};
/// Sampler 数量
static constexpr uint32_t SAMPLER_COUNT = {{ sampler_count }};
// ========================================================================
// Uniform Buffer 结构定义
// ========================================================================
{% if uniform_buffers %}
{% for ub in uniform_buffers %}
{% if ub.members %}
/**
* @brief {{ ub.name }} Uniform Buffer 数据结构
*/
struct {{ ub.struct_name }} {
{% for member in ub.members %}
/// {{ member.name }}
{{ member.cpp_type }} {{ member.name }}{{ '[' + member.array_size|string + ']' if member.array_size > 1 else '' }};
{% endfor %}
};
{% endif %}
{% endfor %}
{% endif %}
// ========================================================================
// Push Constant 结构定义
// ========================================================================
{% if push_constants %}
{% for pc in push_constants %}
{% if pc.members %}
/**
* @brief {{ pc.name }} Push Constant 数据结构
*/
struct {{ pc.struct_name }} {
{% for member in pc.members %}
/// {{ member.name }}
{{ member.cpp_type }} {{ member.name }};
{% endfor %}
};
{% endif %}
{% endfor %}
{% endif %}
// ========================================================================
// 绑定常量 (按资源类型分组)
// ========================================================================
{% if samplers %}
// -------------------- Sampler 绑定常量 --------------------
{% for s in samplers %}
/// {{ s.name }} Set
static constexpr uint32_t {{ s.name | upper }}_SET = {{ s.set }};
/// {{ s.name }} Binding
static constexpr uint32_t {{ s.name | upper }}_BINDING = {{ s.binding }};
{% endfor %}
{% endif %}
{% if uniform_buffers %}
// -------------------- Uniform Buffer 绑定常量 --------------------
{% for ub in uniform_buffers %}
/// {{ ub.name }} Set
static constexpr uint32_t {{ ub.name | upper }}_SET = {{ ub.set }};
/// {{ ub.name }} Binding
static constexpr uint32_t {{ ub.name | upper }}_BINDING = {{ ub.binding }};
{% endfor %}
{% endif %}
// ========================================================================
// 数据成员
// ========================================================================
{% if uniform_buffers %}
{% for ub in uniform_buffers %}
/// {{ ub.name }} 数据
{{ ub.struct_name }} {{ ub.name | lower }}_data{};
{% endfor %}
{% endif %}
{% if push_constants %}
{% for pc in push_constants %}
{% if pc.members %}
/// {{ pc.name }} 数据
{{ pc.struct_name }} {{ pc.name | lower }}_data{};
{% endif %}
{% endfor %}
{% endif %}
// ========================================================================
// SPIR-V 字节码访问方法
// ========================================================================
{% if has_vertex_shader %}
/**
* @brief 获取顶点着色器 SPIR-V 字节码
* @return 顶点着色器字节码视图
*/
[[nodiscard]] static constexpr std::span<const uint32_t> get_vertex_spirv() noexcept {
{% if stages.VERT %}
static constexpr std::array<uint32_t, {{ stages.VERT.word_count }}> VERT_SPIRV = {
{% for line in stages.VERT.hex_lines %}
{{ line }}
{% endfor %}
};
return std::span<const uint32_t>(VERT_SPIRV);
{% else %}
return {};
{% endif %}
}
{% endif %}
{% if has_fragment_shader %}
/**
* @brief 获取片段着色器 SPIR-V 字节码
* @return 片段着色器字节码视图
*/
[[nodiscard]] static constexpr std::span<const uint32_t> get_fragment_spirv() noexcept {
{% if stages.FRAG %}
static constexpr std::array<uint32_t, {{ stages.FRAG.word_count }}> FRAG_SPIRV = {
{% for line in stages.FRAG.hex_lines %}
{{ line }}
{% endfor %}
};
return std::span<const uint32_t>(FRAG_SPIRV);
{% else %}
return {};
{% endif %}
}
{% endif %}
// ========================================================================
// 描述符集布局创建
// ========================================================================
{% if samplers or uniform_buffers %}
/**
* @brief 创建描述符集布局
* @param device Vulkan 设备
* @return 描述符集布局
*/
[[nodiscard]] static vk::DescriptorSetLayout create_descriptor_set_layout(vk::Device& device) {
std::vector<vk::DescriptorSetLayoutBinding> bindings;
{% if samplers %}
{% for s in samplers %}
// {{ s.name }} - Sampler
bindings.push_back(vk::DescriptorSetLayoutBinding{}
.setBinding({{ s.name | upper }}_BINDING)
.setDescriptorType(vk::DescriptorType::eCombinedImageSampler)
.setDescriptorCount(1)
.setStageFlags(vk::ShaderStageFlagBits::eFragment));
{% endfor %}
{% endif %}
{% if uniform_buffers %}
{% for ub in uniform_buffers %}
// {{ ub.name }} - Uniform Buffer
bindings.push_back(vk::DescriptorSetLayoutBinding{}
.setBinding({{ ub.name | upper }}_BINDING)
.setDescriptorType(vk::DescriptorType::eUniformBuffer)
.setDescriptorCount(1)
.setStageFlags(vk::ShaderStageFlagBits::eFragment));
{% endfor %}
{% endif %}
vk::DescriptorSetLayoutCreateInfo layout_info{};
layout_info.setBindings(bindings);
auto [result, layout] = device.createDescriptorSetLayout(layout_info);
if (result != vk::Result::eSuccess) {
return VK_NULL_HANDLE;
}
return layout;
}
{% else %}
/**
* @brief 创建描述符集布局 (无资源时返回空布局)
* @return VK_NULL_HANDLE
*/
[[nodiscard]] static vk::DescriptorSetLayout create_descriptor_set_layout(vk::Device&) noexcept {
return VK_NULL_HANDLE;
}
{% endif %}
// ========================================================================
// 描述符池创建
// ========================================================================
{% if samplers or uniform_buffers %}
/**
* @brief 创建描述符池
* @param device Vulkan 设备
* @param max_sets 最大描述符集数量
* @return 描述符池
*/
[[nodiscard]] static vk::DescriptorPool create_descriptor_pool(vk::Device& device, uint32_t max_sets = 1) {
std::vector<vk::DescriptorPoolSize> pool_sizes;
{% if samplers %}
pool_sizes.push_back(vk::DescriptorPoolSize{}
.setType(vk::DescriptorType::eCombinedImageSampler)
.setDescriptorCount({{ samplers | length }} * max_sets));
{% endif %}
{% if uniform_buffers %}
pool_sizes.push_back(vk::DescriptorPoolSize{}
.setType(vk::DescriptorType::eUniformBuffer)
.setDescriptorCount({{ uniform_buffers | length }} * max_sets));
{% endif %}
vk::DescriptorPoolCreateInfo pool_info{};
pool_info.setPoolSizes(pool_sizes);
pool_info.setMaxSets(max_sets);
auto [result, pool] = device.createDescriptorPool(pool_info);
if (result != vk::Result::eSuccess) {
return VK_NULL_HANDLE;
}
return pool;
}
{% else %}
/**
* @brief 创建描述符池 (无资源时返回空池)
* @return VK_NULL_HANDLE
*/
[[nodiscard]] static vk::DescriptorPool create_descriptor_pool(vk::Device&, uint32_t = 1) noexcept {
return VK_NULL_HANDLE;
}
{% endif %}
// ========================================================================
// 推送常量范围获取
// ========================================================================
{% if push_constants %}
/**
* @brief 获取推送常量范围列表
* @return 推送常量范围列表
*/
[[nodiscard]] static std::vector<vk::PushConstantRange> get_push_constant_ranges() {
std::vector<vk::PushConstantRange> ranges;
{% for pc in push_constants %}
ranges.push_back(vk::PushConstantRange{}
.setStageFlags(vk::ShaderStageFlagBits::eFragment)
.setOffset({{ pc.offset }})
.setSize({{ pc.size }}));
{% endfor %}
return ranges;
}
{% else %}
/**
* @brief 获取推送常量范围列表 (无推送常量时返回空列表)
* @return 空列表
*/
[[nodiscard]] static std::vector<vk::PushConstantRange> get_push_constant_ranges() {
return {};
}
{% endif %}
// ========================================================================
// 静态检查辅助模板
// ========================================================================
{% if samplers %}
/**
* @brief 检查是否包含指定名称的 Sampler
*/
template<uint32_t Binding>
struct has_sampler_binding : std::false_type {};
{% for s in samplers %}
/**
* @brief {{ s.name }} Sampler 绑定检查
*/
template<>
struct has_sampler_binding<{{ s.name | upper }}_BINDING> : std::true_type {};
{% endfor %}
/**
* @brief 获取指定绑定的 Sampler 名称
*/
template<uint32_t Binding>
static constexpr const char* get_sampler_name() {
{% for s in samplers %}
if constexpr (Binding == {{ s.name | upper }}_BINDING) {
return "{{ s.name }}";
}
{% endfor %}
return nullptr;
}
{% endif %}
{% if uniform_buffers %}
/**
* @brief 检查是否包含指定名称的 Uniform Buffer
*/
template<uint32_t Binding>
struct has_uniform_buffer_binding : std::false_type {};
{% for ub in uniform_buffers %}
/**
* @brief {{ ub.name }} Uniform Buffer 绑定检查
*/
template<>
struct has_uniform_buffer_binding<{{ ub.name | upper }}_BINDING> : std::true_type {};
{% endfor %}
{% endif %}
};
} // namespace mirai::generated

View File

@@ -12,6 +12,14 @@
"name": "sdl3",
"version>=": "3.2.28"
},
{
"name": "spirv-cross",
"version>=": "1.3.296.0"
},
{
"name": "nlohmann-json",
"version>=": "3.12.0#1"
},
{
"name": "stb",
"version>=": "2024-07-29#1"