diff --git a/CMakeLists.txt b/CMakeLists.txt index ca1d0c6..f3cf93a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,6 +121,7 @@ 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) diff --git a/cmake/shader_compile.cmake b/cmake/shader_compile.cmake index 44ada55..aa0a668 100644 --- a/cmake/shader_compile.cmake +++ b/cmake/shader_compile.cmake @@ -1,14 +1,16 @@ # ============================================================================ # MIRAI 着色器编译 CMake 模块 # ============================================================================ -# 提供自动化着色器编译和代码生成功能 -# 自动检测 [shader("xxx")] 属性并编译所有入口点 +# 提供自动化 GLSL 着色器编译和代码生成功能 +# 使用 glslangValidator 或 glslc (shaderc) 编译 GLSL 到 SPIR-V # # 主要函数: -# add_shader_library() - 创建着色器库目标 +# add_glsl_shader_library() - 创建 GLSL 着色器库目标 +# compile_glsl_shader() - 编译单个 GLSL 着色器 # # 辅助函数: # find_python_with_jinja2() - 查找带 Jinja2 的 Python +# find_glsl_compiler() - 查找 GLSL 编译器 # ============================================================================ include_guard(GLOBAL) @@ -44,6 +46,46 @@ function(find_python_with_jinja2 OUT_PYTHON_EXECUTABLE) 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() + # ============================================================================ # 内部变量设置 # ============================================================================ @@ -65,73 +107,188 @@ 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_shader_library()." + "Make sure add_subdirectory(tools/shader_compile) is called before using add_glsl_shader_library()." ) endif() endfunction() # ============================================================================ -# add_shader_library - 添加着色器库到现有目标 +# _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 着色器 # ============================================================================ # -# 将着色器编译和代码生成附加到现有的编译目标上 -# 自动检测所有 [shader("xxx")] 入口点并编译 +# 用法: +# 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_shader_library(TARGET mirai_shader -# SHADER_PATH shaders -# REF_PATHS shaders/common shaders/math +# # 方案1: 自动搜索着色器 +# add_glsl_shader_library(TARGET mirai_shader +# SHADER_PATH shaders/glsl +# INCLUDE_DIRS shaders/glsl/common # ) # # # 方案2: 指定具体着色器文件 -# add_shader_library(TARGET mirai_shader -# SHADERS shaders/ui/rect.slang shaders/ui/text.slang -# REF_PATHS shaders/common -# ) -# -# # 方案3: 混合模式,添加额外着色器 -# add_shader_library(TARGET mirai_shader -# SHADER_PATH shaders -# SHADERS shaders/ui/extra.slang -# REF_PATHS shaders/common +# 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(可选) -# REF_PATHS - 着色器引用路径,用于 include 通用模块(可选) +# INCLUDE_DIRS - 着色器 include 路径(可选) # DEFINES - 预处理器定义(可选) # RECURSIVE - 是否递归搜索子目录(可选,默认 OFF) # -function(add_shader_library) +function(add_glsl_shader_library) # 解析参数 set(options RECURSIVE) set(oneValueArgs TARGET SHADER_PATH) - set(multiValueArgs SHADERS REF_PATHS DEFINES) + set(multiValueArgs SHADERS INCLUDE_DIRS DEFINES) cmake_parse_arguments(SHADER "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) # 验证必需参数 if(NOT SHADER_TARGET) - message(FATAL_ERROR "add_shader_library: TARGET is required") + message(FATAL_ERROR "add_glsl_shader_library: TARGET is required") endif() # 验证目标存在 if(NOT TARGET ${SHADER_TARGET}) - message(FATAL_ERROR "add_shader_library: Target '${SHADER_TARGET}' does not exist") + 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) - retrieve_files_custom(${CMAKE_CURRENT_SOURCE_DIR} "slang" FOUND_SHADERS) + 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() - file(GLOB FOUND_SHADERS "${CMAKE_CURRENT_SOURCE_DIR}/${SHADER_SHADER_PATH}/*.slang") + foreach(EXT ${SHADER_EXTENSIONS}) + file(GLOB FOUND_SHADERS "${CMAKE_CURRENT_SOURCE_DIR}/${SHADER_SHADER_PATH}/*.${EXT}") + list(APPEND ALL_SHADERS ${FOUND_SHADERS}) + endforeach() endif() - list(APPEND ALL_SHADERS ${FOUND_SHADERS}) endif() # 去重 @@ -152,71 +309,145 @@ function(add_shader_library) # 查找 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_REF_PATHS}) + foreach(INC_DIR ${SHADER_INCLUDE_DIRS}) if(IS_ABSOLUTE "${INC_DIR}") - list(APPEND INCLUDE_ARGS "-I${INC_DIR}") + list(APPEND INCLUDE_ARGS "${INC_DIR}") else() - list(APPEND INCLUDE_ARGS "-I${CMAKE_CURRENT_SOURCE_DIR}/${INC_DIR}") + list(APPEND INCLUDE_ARGS "${CMAKE_CURRENT_SOURCE_DIR}/${INC_DIR}") endif() endforeach() - # 构建宏定义参数 - set(DEFINE_ARGS "") - foreach(DEF ${SHADER_DEFINES}) - list(APPEND DEFINE_ARGS "-D${DEF}") - 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) - if(IS_ABSOLUTE "${SHADER_SOURCE}") - set(SHADER_ABS "${SHADER_SOURCE}") - else() - set(SHADER_ABS "${CMAKE_CURRENT_SOURCE_DIR}/${SHADER_SOURCE}") - if(NOT EXISTS "${SHADER_ABS}") - message(FATAL_ERROR "Shader not found: ${SHADER_SOURCE}") + # 创建唯一的组标识符 + 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 $ + --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() - - # 每个着色器文件的输出目录 - set(SPIRV_OUTPUT_DIR "${SHADER_INTERMEDIATE_DIR}/${SHADER_NAME_WE}") - set(GENERATED_HPP "${SHADER_OUTPUT_DIR}/${SHADER_NAME_WE}_bindings.hpp") - - # 步骤1: 编译着色器(自动检测所有入口点) - add_custom_command( - OUTPUT "${SPIRV_OUTPUT_DIR}/.compiled" - COMMAND ${CMAKE_COMMAND} -E make_directory "${SPIRV_OUTPUT_DIR}" - COMMAND $ - "${SHADER_ABS}" - -o "${SPIRV_OUTPUT_DIR}" - ${INCLUDE_ARGS} - ${DEFINE_ARGS} - COMMAND ${CMAKE_COMMAND} -E touch "${SPIRV_OUTPUT_DIR}/.compiled" - DEPENDS "${SHADER_ABS}" ${MIRAI_SHADER_COMPILER_TARGET} - COMMENT "Compiling shader: ${SHADER_NAME_WE}" - VERBATIM COMMAND_EXPAND_LISTS - ) - - # 步骤2: 生成绑定头文件(Python 脚本自己查找 spv 文件) - add_custom_command( - OUTPUT "${GENERATED_HPP}" - COMMAND ${PYTHON_EXECUTABLE} "${MIRAI_SHADER_GENERATOR_SCRIPT}" - --dir "${SPIRV_OUTPUT_DIR}" - --output "${GENERATED_HPP}" - --name "${SHADER_NAME_WE}" - --template-dir "${MIRAI_SHADER_TEMPLATE_DIR}" - DEPENDS "${SPIRV_OUTPUT_DIR}/.compiled" "${MIRAI_SHADER_GENERATOR_SCRIPT}" - COMMENT "Generating bindings: ${SHADER_NAME_WE}" - VERBATIM - ) - - list(APPEND ALL_GENERATED_HEADERS "${GENERATED_HPP}") endforeach() # 附加到目标:添加生成目录到 include 路径 @@ -233,10 +464,52 @@ function(add_shader_library) # 导出变量 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 shaders to target: ${SHADER_TARGET}") + message(STATUS "Added GLSL shaders to target: ${SHADER_TARGET}") message(STATUS " Output: ${SHADER_OUTPUT_DIR}") if(ALL_SHADERS) - message(STATUS " Shaders: ${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() diff --git a/docs/adr/adr-002-slang-shader-system.md b/docs/adr/adr-002-slang-shader-system.md index a1904b3..9194aab 100644 --- a/docs/adr/adr-002-slang-shader-system.md +++ b/docs/adr/adr-002-slang-shader-system.md @@ -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 ## 背景 diff --git a/docs/adr/adr-003-glsl-shader-system.md b/docs/adr/adr-003-glsl-shader-system.md new file mode 100644 index 0000000..d8fd271 --- /dev/null +++ b/docs/adr/adr-003-glsl-shader-system.md @@ -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` → `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) \ No newline at end of file diff --git a/docs/shader_widget_architecture.md b/docs/shader_widget_architecture.md new file mode 100644 index 0000000..8144876 --- /dev/null +++ b/docs/shader_widget_architecture.md @@ -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 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) - 使用示例 \ No newline at end of file diff --git a/docs/shader_widget_examples.md b/docs/shader_widget_examples.md new file mode 100644 index 0000000..8717993 --- /dev/null +++ b/docs/shader_widget_examples.md @@ -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(¶ms_, 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 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) - 详细设计文档 \ No newline at end of file diff --git a/plans/glsl_shader_system_design.md b/plans/glsl_shader_system_design.md new file mode 100644 index 0000000..53140d3 --- /dev/null +++ b/plans/glsl_shader_system_design.md @@ -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 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) | \ No newline at end of file diff --git a/src/shader/CMakeLists.txt b/src/shader/CMakeLists.txt index b0db72f..8dc54b9 100644 --- a/src/shader/CMakeLists.txt +++ b/src/shader/CMakeLists.txt @@ -7,12 +7,12 @@ project(mirai_shader) simple_library(STATIC) target_link_libraries(${PROJECT_NAME} PUBLIC mirai_core mirai_render mirai_resource Vulkan::Vulkan) -# 添加着色器编译到现有目标 -# 自动搜索 shaders 目录下的所有 .slang 文件 -# shaders/common 和 shaders/math 用于着色器模块引用 -add_shader_library(TARGET ${PROJECT_NAME} - SHADER_PATH shaders - REF_PATHS shaders/common shaders/math +# 添加 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 @@ -27,4 +27,4 @@ install(TARGETS ${PROJECT_NAME} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) -message(STATUS "Configured mirai_shader library") \ No newline at end of file +message(STATUS "Configured mirai_shader library with GLSL shaders") \ No newline at end of file diff --git a/src/shader/shaders/basic.slang b/src/shader/shaders/basic.slang deleted file mode 100644 index d2374cf..0000000 --- a/src/shader/shaders/basic.slang +++ /dev/null @@ -1,159 +0,0 @@ -/** - * @file basic.slang - * @brief 基础 UI 着色器示例 - * @description 展示 GUI 渲染中常用的 Uniform Buffer、纹理采样、顶点属性、Push Constants - * 以及多入口点支持(通过 [shader("xxx")] 属性自动检测) - */ - -// ============================================================================ -// Uniform Buffer 定义 -// ============================================================================ - -/** - * @brief 全局变换 Uniform Buffer - * @set 0 - * @binding 0 - */ -struct GlobalUniforms { - float4x4 projection; // 正交投影矩阵 - float2 screenSize; // 屏幕尺寸 - float2 _padding; // 对齐填充 -}; - -[[vk::binding(0, 0)]] -ConstantBuffer globals; - -// ============================================================================ -// 纹理和采样器 -// ============================================================================ - -/** - * @brief UI 纹理(图标、图片等) - */ -[[vk::binding(0, 1)]] -Texture2D uiTexture; - -/** - * @brief 线性采样器 - */ -[[vk::binding(1, 1)]] -SamplerState linearSampler; - -/** - * @brief 计算着色器用的输入纹理 - */ -[[vk::binding(0, 2)]] -RWTexture2D computeInput; - -/** - * @brief 计算着色器用的输出纹理 - */ -[[vk::binding(1, 2)]] -RWTexture2D computeOutput; - -// ============================================================================ -// Push Constants -// ============================================================================ - -/** - * @brief 每次绘制调用的参数 - */ -struct PushConstants { - float4 color; // 顶点颜色乘数 - float2 offset; // 位置偏移 - float opacity; // 不透明度 - uint useTexture; // 是否使用纹理 (0 或 1) -}; - -[[vk::push_constant]] -PushConstants pc; - -/** - * @brief 计算着色器的 Push Constants - */ -struct ComputePushConstants { - uint2 workgroupSize; // 工作组大小 - uint2 workgroupCount; // 工作组数量 - float intensity; // 强度参数 -}; - -[[vk::push_constant]] -ComputePushConstants computePc; - -// ============================================================================ -// 顶点输入输出结构 -// ============================================================================ - -/** - * @brief 顶点着色器输入 - */ -struct VertexInput { - [[vk::location(0)]] float2 position : POSITION; // 2D 位置 - [[vk::location(1)]] float2 texCoord : TEXCOORD0; // 纹理坐标 - [[vk::location(2)]] float4 color : COLOR0; // 顶点颜色 -}; - -/** - * @brief 顶点着色器输出 - */ -struct VertexOutput { - float4 position : SV_Position; - float2 texCoord : TEXCOORD0; - float4 color : COLOR0; -}; - -// ============================================================================ -// 顶点着色器 -// ============================================================================ - -[shader("vertex")] -VertexOutput vertexMain(VertexInput input) { - VertexOutput output; - - // 应用偏移并变换到裁剪空间 - float2 pos = input.position + pc.offset; - output.position = mul(globals.projection, float4(pos, 0.0f, 1.0f)); - - // 传递纹理坐标和颜色 - output.texCoord = input.texCoord; - output.color = input.color * pc.color; - - return output; -} - -// ============================================================================ -// 片段着色器 -// ============================================================================ - -[shader("fragment")] -float4 fragmentMain(VertexOutput input) : SV_Target { - float4 color = input.color; - - // 如果使用纹理,采样并相乘 - if (pc.useTexture != 0) { - float4 texColor = uiTexture.Sample(linearSampler, input.texCoord); - color *= texColor; - } - - // 应用不透明度 - color.a *= pc.opacity; - - return color; -} - -// ============================================================================ -// 计算着色器 -// ============================================================================ - -/** - * @brief 简单的图像处理计算着色器 - * @description 演示多入口点支持 - 自动检测 [shader("compute")] - */ -[shader("compute")] -void computeMain(uint3 globalID : SV_DispatchThreadID) { - // 简单的图像处理:应用强度 - float4 color = computeInput[globalID.xy]; - color.rgb *= computePc.intensity; - color.a = 1.0f; - computeOutput[globalID.xy] = color; -} diff --git a/src/shader/shaders/glsl/examples/blur.frag b/src/shader/shaders/glsl/examples/blur.frag new file mode 100644 index 0000000..471b7de --- /dev/null +++ b/src/shader/shaders/glsl/examples/blur.frag @@ -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; +} \ No newline at end of file diff --git a/src/shader/shaders/glsl/examples/gradient.frag b/src/shader/shaders/glsl/examples/gradient.frag new file mode 100644 index 0000000..53ca82b --- /dev/null +++ b/src/shader/shaders/glsl/examples/gradient.frag @@ -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; +} \ No newline at end of file diff --git a/src/shader/shaders/glsl/examples/rounded_rect.frag b/src/shader/shaders/glsl/examples/rounded_rect.frag new file mode 100644 index 0000000..eeaaff1 --- /dev/null +++ b/src/shader/shaders/glsl/examples/rounded_rect.frag @@ -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; +} \ No newline at end of file diff --git a/src/shader/shaders/glsl/framework/fullscreen.vert b/src/shader/shaders/glsl/framework/fullscreen.vert new file mode 100644 index 0000000..811ec6d --- /dev/null +++ b/src/shader/shaders/glsl/framework/fullscreen.vert @@ -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]; +} \ No newline at end of file diff --git a/src/shader/shaders/glsl/framework/quad.vert b/src/shader/shaders/glsl/framework/quad.vert new file mode 100644 index 0000000..b3df275 --- /dev/null +++ b/src/shader/shaders/glsl/framework/quad.vert @@ -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; +} \ No newline at end of file diff --git a/src/shader/shaders/glsl/templates/backdrop.frag.template b/src/shader/shaders/glsl/templates/backdrop.frag.template new file mode 100644 index 0000000..43158c2 --- /dev/null +++ b/src/shader/shaders/glsl/templates/backdrop.frag.template @@ -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; +} \ No newline at end of file diff --git a/src/shader/shaders/glsl/templates/mask.frag.template b/src/shader/shaders/glsl/templates/mask.frag.template new file mode 100644 index 0000000..c763c70 --- /dev/null +++ b/src/shader/shaders/glsl/templates/mask.frag.template @@ -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; +} \ No newline at end of file diff --git a/src/shader/shaders/glsl/templates/mesh.vert.template b/src/shader/shaders/glsl/templates/mesh.vert.template new file mode 100644 index 0000000..2661909 --- /dev/null +++ b/src/shader/shaders/glsl/templates/mesh.vert.template @@ -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; +} \ No newline at end of file diff --git a/src/shader/shaders/glsl/templates/procedural.frag.template b/src/shader/shaders/glsl/templates/procedural.frag.template new file mode 100644 index 0000000..ceabe00 --- /dev/null +++ b/src/shader/shaders/glsl/templates/procedural.frag.template @@ -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; +} \ No newline at end of file diff --git a/src/shader/shaders/math.slang b/src/shader/shaders/math.slang deleted file mode 100644 index 788fa26..0000000 --- a/src/shader/shaders/math.slang +++ /dev/null @@ -1,200 +0,0 @@ -/** - * @file math.slang - * @brief 公共数学函数模块 - * @description 提供常用的数学计算函数,供其他着色器模块导入使用 - */ - -// ============================================================================ -// 常量定义 -// ============================================================================ - -static const float PI = 3.14159265358979323846f; -static const float TWO_PI = 6.28318530717958647692f; -static const float HALF_PI = 1.57079632679489661923f; -static const float INV_PI = 0.31830988618379067154f; -static const float EPSILON = 1e-6f; - -// ============================================================================ -// 基础数学函数 -// ============================================================================ - -/** - * @brief 将值限制在 [0, 1] 范围内 - */ -float saturate(float x) { - return clamp(x, 0.0f, 1.0f); -} - -float2 saturate(float2 v) { - return clamp(v, float2(0.0f), float2(1.0f)); -} - -float3 saturate(float3 v) { - return clamp(v, float3(0.0f), float3(1.0f)); -} - -float4 saturate(float4 v) { - return clamp(v, float4(0.0f), float4(1.0f)); -} - -/** - * @brief 平滑插值(Hermite 插值) - */ -float smoothstep01(float x) { - float t = saturate(x); - return t * t * (3.0f - 2.0f * t); -} - -/** - * @brief 更平滑的插值(5次多项式) - */ -float smootherstep(float x) { - float t = saturate(x); - return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); -} - -// ============================================================================ -// 向量操作 -// ============================================================================ - -/** - * @brief 计算向量的平方长度(避免 sqrt) - */ -float lengthSq(float2 v) { - return dot(v, v); -} - -float lengthSq(float3 v) { - return dot(v, v); -} - -float lengthSq(float4 v) { - return dot(v, v); -} - -/** - * @brief 安全的向量归一化(避免除零) - */ -float3 safeNormalize(float3 v) { - float len = length(v); - return len > EPSILON ? v / len : float3(0.0f, 0.0f, 1.0f); -} - -/** - * @brief 计算反射向量 - */ -float3 reflectVector(float3 incident, float3 normal) { - return incident - 2.0f * dot(incident, normal) * normal; -} - -// ============================================================================ -// 颜色空间转换 -// ============================================================================ - -/** - * @brief 线性空间到 sRGB - */ -float linearToSrgb(float linear) { - if (linear <= 0.0031308f) { - return linear * 12.92f; - } - return 1.055f * pow(linear, 1.0f / 2.4f) - 0.055f; -} - -float3 linearToSrgb(float3 linear) { - return float3( - linearToSrgb(linear.x), - linearToSrgb(linear.y), - linearToSrgb(linear.z) - ); -} - -/** - * @brief sRGB 到线性空间 - */ -float srgbToLinear(float srgb) { - if (srgb <= 0.04045f) { - return srgb / 12.92f; - } - return pow((srgb + 0.055f) / 1.055f, 2.4f); -} - -float3 srgbToLinear(float3 srgb) { - return float3( - srgbToLinear(srgb.x), - srgbToLinear(srgb.y), - srgbToLinear(srgb.z) - ); -} - -/** - * @brief 计算亮度(Luminance) - */ -float luminance(float3 color) { - return dot(color, float3(0.2126f, 0.7152f, 0.0722f)); -} - -// ============================================================================ -// 矩阵工具函数 -// ============================================================================ - -/** - * @brief 创建绕 Z 轴旋转的 2D 旋转矩阵 - */ -float2x2 rotate2D(float angle) { - float c = cos(angle); - float s = sin(angle); - return float2x2(c, -s, s, c); -} - -/** - * @brief 创建 2D 缩放矩阵 - */ -float2x2 scale2D(float2 scale) { - return float2x2(scale.x, 0.0f, 0.0f, scale.y); -} - -// ============================================================================ -// 噪声函数 -// ============================================================================ - -/** - * @brief 简单的伪随机哈希函数 - */ -float hash11(float p) { - p = frac(p * 0.1031f); - p *= p + 33.33f; - p *= p + p; - return frac(p); -} - -float hash21(float2 p) { - float3 p3 = frac(float3(p.x, p.y, p.x) * 0.1031f); - p3 += dot(p3, p3.yzx + 33.33f); - return frac((p3.x + p3.y) * p3.z); -} - -float2 hash22(float2 p) { - float3 p3 = frac(float3(p.x, p.y, p.x) * float3(0.1031f, 0.1030f, 0.0973f)); - p3 += dot(p3, p3.yzx + 33.33f); - return frac((p3.xx + p3.yz) * p3.zy); -} - -/** - * @brief 值噪声 - */ -float valueNoise(float2 p) { - float2 i = floor(p); - float2 f = frac(p); - - // 四个角的随机值 - float a = hash21(i); - float b = hash21(i + float2(1.0f, 0.0f)); - float c = hash21(i + float2(0.0f, 1.0f)); - float d = hash21(i + float2(1.0f, 1.0f)); - - // 平滑插值 - float2 u = f * f * (3.0f - 2.0f * f); - - return lerp(lerp(a, b, u.x), lerp(c, d, u.x), u.y); -} \ No newline at end of file diff --git a/src/widget/CMakeLists.txt b/src/widget/CMakeLists.txt new file mode 100644 index 0000000..635b3fc --- /dev/null +++ b/src/widget/CMakeLists.txt @@ -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") \ No newline at end of file diff --git a/src/widget/effects.hpp b/src/widget/effects.hpp new file mode 100644 index 0000000..546ec9c --- /dev/null +++ b/src/widget/effects.hpp @@ -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(); + * auto gradient = std::make_shared(); + * auto mask = std::make_shared(); + * auto particles = std::make_shared(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; + +/// 渐变效果控件指针类型 +using gradient_widget_ptr = std::shared_ptr; + +/// 圆角遮罩控件指针类型 +using rounded_rect_mask_widget_ptr = std::shared_ptr; + +/// 粒子系统控件指针类型 +using particle_widget_ptr = std::shared_ptr; + +/** @} */ + +// ============================================================================ +// 工厂函数 +// ============================================================================ + +/** + * @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(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(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(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( + 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(count); +} + +/** @} */ + +} // namespace mirai \ No newline at end of file diff --git a/src/widget/effects/blur_widget.hpp b/src/widget/effects/blur_widget.hpp new file mode 100644 index 0000000..2087f93 --- /dev/null +++ b/src/widget/effects/blur_widget.hpp @@ -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->set_blur_radius(10.0f); + * blur->set_blur_quality(blur_quality::medium); + * @endcode + */ + +#pragma once + +#include "shader_widget.hpp" +#include "" + +#include +#include + +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 { +public: + MIRAI_OBJECT_TYPE_INFO(blur_widget, backdrop_widget) + + // ============================================================================================ + // 构造与析构 + // ============================================================================================ + + /** + * @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(quality_); + + // TODO: 将 params 写入 uniform buffer + // uniform_buffer_->write(¶ms, sizeof(params)); + } + +private: + // ============================================================================================ + // 成员变量 + // ============================================================================================ + + /// 模糊半径(像素) + f32 blur_radius_; + + /// 模糊质量 + blur_quality quality_; +}; + +} // namespace mirai \ No newline at end of file diff --git a/src/widget/effects/gradient_widget.hpp b/src/widget/effects/gradient_widget.hpp new file mode 100644 index 0000000..11fa057 --- /dev/null +++ b/src/widget/effects/gradient_widget.hpp @@ -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->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 +#include +#include + +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 get_vertex_spirv() { + // 占位:实际数据由着色器编译生成 + static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number + return std::span(placeholder); + } + + /** + * @brief 获取片段着色器 SPIR-V 数据 + * @return SPIR-V 字节码的 span + * @note 实际实现中,此数据由编译后的着色器提供 + */ + [[nodiscard]] static std::span get_fragment_spirv() { + // 占位:实际数据由着色器编译生成 + static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number + return std::span(placeholder); + } +}; + +// ================================================================================================ +// gradient_widget 类 +// ================================================================================================ + +/** + * @brief 渐变效果控件 + * + * 继承自 procedural_widget,实现程序化渐变效果。 + * + * 特点: + * - 支持四种渐变类型(线性、径向、角度、菱形) + * - 可自定义起始和结束颜色 + * - 支持渐变角度和中心点设置 + * - 支持重复模式 + * + * @see procedural_widget + * @see gradient_bindings + */ +class gradient_widget : public procedural_widget { +public: + MIRAI_OBJECT_TYPE_INFO(gradient_widget, procedural_widget) + + // ============================================================================================ + // 构造与析构 + // ============================================================================================ + + /** + * @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(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(¶ms, sizeof(params)); + } + +private: + // ============================================================================================ + // 成员变量 + // ============================================================================================ + + /// 起始颜色 + color start_color_; + + /// 结束颜色 + color end_color_; + + /// 渐变类型 + gradient_type type_; + + /// 渐变角度(弧度) + f32 angle_; + + /// 渐变中心 + f32 center_[2]; + + /// 渐变半径 + f32 radius_; + + /// 重复次数 + f32 repeat_count_; +}; + +} // namespace mirai \ No newline at end of file diff --git a/src/widget/effects/particle_widget.hpp b/src/widget/effects/particle_widget.hpp new file mode 100644 index 0000000..b04786b --- /dev/null +++ b/src/widget/effects/particle_widget.hpp @@ -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(); + * 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 +#include +#include +#include + +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 get_vertex_spirv() { + // 占位:实际数据由着色器编译生成 + static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number + return std::span(placeholder); + } + + /** + * @brief 获取片段着色器 SPIR-V 数据 + * @return SPIR-V 字节码的 span + * @note 实际实现中,此数据由编译后的着色器提供 + */ + [[nodiscard]] static std::span get_fragment_spirv() { + // 占位:实际数据由着色器编译生成 + static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number + return std::span(placeholder); + } +}; + +// ================================================================================================ +// particle_widget 类 +// ================================================================================================ + +/** + * @brief 粒子系统控件 + * + * 继承自 mesh_widget,使用实例化渲染绘制大量粒子。 + * + * 特点: + * - 支持大量粒子的高效渲染 + * - 多种渲染模式(Billboard、速度对齐、拉伸) + * - 生命周期颜色和大小动画 + * - 淡入淡出效果 + * + * @see mesh_widget + * @see particle_bindings + */ +class particle_widget : public mesh_widget { +public: + MIRAI_OBJECT_TYPE_INFO(particle_widget, mesh_widget) + + // ============================================================================================ + // 构造与析构 + // ============================================================================================ + + /** + * @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& particles() { return particles_; } + + /** + * @brief 获取粒子数据(只读) + * @return 粒子数据向量的常量引用 + */ + [[nodiscard]] const std::vector& 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(mode_); + params.stretch_factor = stretch_factor_; + + // TODO: 将 params 写入 uniform buffer + // uniform_buffer_->write(¶ms, 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 particles_; +}; + +} // namespace mirai \ No newline at end of file diff --git a/src/widget/effects/rounded_rect_mask_widget.hpp b/src/widget/effects/rounded_rect_mask_widget.hpp new file mode 100644 index 0000000..5f460da --- /dev/null +++ b/src/widget/effects/rounded_rect_mask_widget.hpp @@ -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(); + * 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 +#include +#include + +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 get_vertex_spirv() { + // 占位:实际数据由着色器编译生成 + static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number + return std::span(placeholder); + } + + /** + * @brief 获取片段着色器 SPIR-V 数据 + * @return SPIR-V 字节码的 span + * @note 实际实现中,此数据由编译后的着色器提供 + */ + [[nodiscard]] static std::span get_fragment_spirv() { + // 占位:实际数据由着色器编译生成 + static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number + return std::span(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 { +public: + MIRAI_OBJECT_TYPE_INFO(rounded_rect_mask_widget, mask_widget) + + // ============================================================================================ + // 构造与析构 + // ============================================================================================ + + /** + * @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(¶ms, sizeof(params)); + } + +private: + // ============================================================================================ + // 成员变量 + // ============================================================================================ + + /// 四角圆角半径 (top_left, top_right, bottom_right, bottom_left) + f32 corner_radius_[4]; + + /// 边缘羽化量 + f32 feather_; + + /// 边框宽度(0 = 填充模式) + f32 border_width_; +}; + +} // namespace mirai \ No newline at end of file diff --git a/src/widget/shader_widget.hpp b/src/widget/shader_widget.hpp new file mode 100644 index 0000000..2ce81dd --- /dev/null +++ b/src/widget/shader_widget.hpp @@ -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 +#include + +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 { + * 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 + requires shader_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 vertex_spirv() { + return Bindings::get_vertex_spirv(); + } + + /** + * @brief 获取片段着色器 SPIR-V 数据 + * @return SPIR-V 字节码的 span + */ + [[nodiscard]] static std::span 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 descriptor_sets_; +}; + +// ================================================================================================ +// 特化模式模板类(可选扩展) +// ================================================================================================ + +/** + * @brief Backdrop 模式控件基类模板 + * + * 用于后效处理模式的控件。提供背景纹理采样能力。 + * + * @tparam Bindings 满足 backdrop_bindings concept 的绑定类型 + */ +template + requires backdrop_bindings +class backdrop_widget : public shader_widget { +public: + MIRAI_OBJECT_TYPE_INFO(backdrop_widget, shader_widget) + + /** + * @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 + requires procedural_bindings +class procedural_widget : public shader_widget { +public: + MIRAI_OBJECT_TYPE_INFO(procedural_widget, shader_widget) + + /** + * @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 + requires mask_bindings +class mask_widget : public shader_widget { +public: + MIRAI_OBJECT_TYPE_INFO(mask_widget, shader_widget) + + /** + * @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 + requires mesh_bindings +class mesh_widget : public shader_widget { +public: + MIRAI_OBJECT_TYPE_INFO(mesh_widget, shader_widget) + + /// 顶点类型别名 + 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 \ No newline at end of file diff --git a/src/widget/shader_widget_base.cpp b/src/widget/shader_widget_base.cpp new file mode 100644 index 0000000..bae8e55 --- /dev/null +++ b/src/widget/shader_widget_base.cpp @@ -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 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(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, + // 但我们这里只有原始指针。在实际使用中, + // 可能需要通过其他方式获取 shared_ptr, + // 或者修改 buffer 类接口。 + // + // 这里返回 nullptr 作为占位实现, + // 实际的 buffer 创建需要在子类中处理, + // 因为子类可能有访问 shared_ptr 的方式。 + + // TODO: 实现实际的 buffer 创建 + // 当前返回空指针,子类需要重写此方法或使用其他方式创建 buffer + return nullptr; +} + +} // namespace mirai \ No newline at end of file diff --git a/src/widget/shader_widget_base.hpp b/src/widget/shader_widget_base.hpp new file mode 100644 index 0000000..acfde27 --- /dev/null +++ b/src/widget/shader_widget_base.hpp @@ -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 + +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 使用 + * class my_widget : public shader_widget { + * // 自定义实现 + * }; + * @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 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 \ No newline at end of file diff --git a/src/widget/widget_types.hpp b/src/widget/widget_types.hpp new file mode 100644 index 0000000..412f7e6 --- /dev/null +++ b/src/widget/widget_types.hpp @@ -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 +#include +#include + +namespace mirai { + +// ================================================================================================ +// 前向声明 +// ================================================================================================ + +class vulkan_device; +class gpu_allocator; +class descriptor_pool; +class command_buffer; +class texture_view; +class sampler; + +// ================================================================================================ +// 着色器绑定 Concepts +// ================================================================================================ + +/** + * @brief 基础绑定检查 + * + * 所有绑定类型必须满足此 concept,提供基本的着色器信息 + */ +template +concept shader_bindings = requires { + /// 是否有顶点着色器 + { T::HAS_VERTEX_SHADER } -> std::convertible_to; + /// 是否有片段着色器 + { T::HAS_FRAGMENT_SHADER } -> std::convertible_to; + /// 获取顶点着色器 SPIR-V 数据 + { T::get_vertex_spirv() } -> std::same_as>; + /// 获取片段着色器 SPIR-V 数据 + { T::get_fragment_spirv() } -> std::same_as>; +}; + +/** + * @brief Backdrop 模式绑定检查 + * + * 用于后效处理模式的绑定类型必须满足此 concept + */ +template +concept backdrop_bindings = shader_bindings && requires { + /// 是否有背景采样器 + { T::HAS_BACKDROP_SAMPLER } -> std::convertible_to; + /// 背景纹理绑定点 + { T::BACKDROP_BINDING } -> std::convertible_to; + /// 背景纹理所在描述符集 + { T::BACKDROP_SET } -> std::convertible_to; +}; + +/** + * @brief Procedural 模式绑定检查 + * + * 用于程序化渲染模式的绑定类型必须满足此 concept + */ +template +concept procedural_bindings = shader_bindings && requires { + /// 是否有帧数据 + { T::HAS_FRAME_DATA } -> std::convertible_to; + /// 帧数据绑定点 + { T::FRAME_DATA_BINDING } -> std::convertible_to; + /// 帧数据所在描述符集 + { T::FRAME_DATA_SET } -> std::convertible_to; +}; + +/** + * @brief Mask 模式绑定检查 + * + * 用于遮罩效果模式的绑定类型必须满足此 concept + */ +template +concept mask_bindings = shader_bindings && requires { + /// 是否有遮罩采样器 + { T::HAS_MASK_SAMPLER } -> std::convertible_to; + /// 遮罩纹理绑定点 + { T::MASK_BINDING } -> std::convertible_to; + /// 遮罩纹理所在描述符集 + { T::MASK_SET } -> std::convertible_to; +}; + +/** + * @brief Mesh 模式绑定检查 + * + * 用于自定义网格渲染模式的绑定类型必须满足此 concept + */ +template +concept mesh_bindings = shader_bindings && requires { + /// 是否有自定义顶点输入 + { T::HAS_CUSTOM_VERTEX_INPUT } -> std::convertible_to; + /// 顶点类型 + 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(static_cast(a) | static_cast(b)); +} + +/** + * @brief 脏标记按位与运算 + * @param a 第一个操作数 + * @param b 第二个操作数 + * @return 运算结果 + */ +constexpr widget_dirty_flags operator&(widget_dirty_flags a, widget_dirty_flags b) { + return static_cast(static_cast(a) & static_cast(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(~static_cast(a)); +} + +/** + * @brief 检查是否包含指定标记 + * @param flags 标记集合 + * @param flag 要检查的标记 + * @return 如果包含返回 true + */ +[[nodiscard]] constexpr bool has_flag(widget_dirty_flags flags, widget_dirty_flags flag) { + return (static_cast(flags) & static_cast(flag)) != 0; +} + +} // namespace mirai \ No newline at end of file diff --git a/tools/shader_compile/CMakeLists.txt b/tools/shader_compile/CMakeLists.txt index 4ca245b..97dba5a 100644 --- a/tools/shader_compile/CMakeLists.txt +++ b/tools/shader_compile/CMakeLists.txt @@ -1,10 +1,13 @@ # tools/shader_compile/CMakeLists.txt # MIRAI 着色器编译器工具 +# 用于从 SPIR-V 提取反射信息并生成绑定代码 cmake_minimum_required(VERSION 3.20) -# 查找 Slang SDK (通过 vcpkg) -find_package(slang CONFIG REQUIRED) +# 查找 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) @@ -17,7 +20,9 @@ add_executable(mirai_shader_compile target_compile_features(mirai_shader_compile PRIVATE cxx_std_20) target_link_libraries(mirai_shader_compile PRIVATE - slang::slang + spirv-cross-core + spirv-cross-glsl + spirv-cross-reflect nlohmann_json::nlohmann_json ) diff --git a/tools/shader_compile/compiler.cpp b/tools/shader_compile/compiler.cpp index b441f2f..129fd93 100644 --- a/tools/shader_compile/compiler.cpp +++ b/tools/shader_compile/compiler.cpp @@ -1,10 +1,11 @@ // tools/shader_compile/compiler.cpp -// MIRAI Shader Compiler Implementation +// MIRAI 着色器反射工具实现 +// 使用 spirv-cross 从 SPIR-V 提取反射信息 #include "compiler.hpp" -#include -#include +#include +#include #include @@ -48,1031 +49,471 @@ const char* shader_stage_to_string(shader_stage stage) { 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"; } -static SlangStage to_slang_stage(shader_stage stage) { - switch (stage) { - case shader_stage::vertex: return SLANG_STAGE_VERTEX; - case shader_stage::fragment: return SLANG_STAGE_FRAGMENT; - case shader_stage::compute: return SLANG_STAGE_COMPUTE; - case shader_stage::geometry: return SLANG_STAGE_GEOMETRY; - case shader_stage::tessellation_control: return SLANG_STAGE_HULL; - case shader_stage::tessellation_evaluation: return SLANG_STAGE_DOMAIN; +shader_stage stage_from_extension(const std::string& ext) { + std::string ext_lower = ext; + for (auto& c : ext_lower) { + c = static_cast(std::tolower(static_cast(c))); } - return SLANG_STAGE_NONE; + + // 处理 .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; } -std::optional slang_stage_to_shader_stage(SlangStage stage) { - switch (stage) { - case SLANG_STAGE_VERTEX: return shader_stage::vertex; - case SLANG_STAGE_FRAGMENT: return shader_stage::fragment; - case SLANG_STAGE_COMPUTE: return shader_stage::compute; - case SLANG_STAGE_GEOMETRY: return shader_stage::geometry; - case SLANG_STAGE_HULL: return shader_stage::tessellation_control; - case SLANG_STAGE_DOMAIN: return shader_stage::tessellation_evaluation; - default: return std::nullopt; +// ============================================================================ +// 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"; } } // ============================================================================ -// Implementation Class +// spirv_reflector Implementation // ============================================================================ -struct shader_compiler::impl { - Slang::ComPtr global_session; - - impl() { - SlangGlobalSessionDesc desc = {}; - slang::createGlobalSession(&desc, global_session.writeRef()); - } - - ~impl() = default; - - compile_result compile( - const std::string& source, - const std::string& filename, - const compile_options& options - ) { - compile_result result; - - if (!global_session) { - result.error_message = "Slang global session not initialized"; - return result; - } - - // Create session options - slang::SessionDesc session_desc = {}; - - // Set target to SPIR-V - slang::TargetDesc target_desc = {}; - target_desc.format = SLANG_SPIRV; - target_desc.profile = global_session->findProfile("glsl_450"); - - if (options.generate_debug_info) { - target_desc.flags |= SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY; - } - - session_desc.targets = &target_desc; - session_desc.targetCount = 1; - - // Store include paths strings to keep them alive - std::vector include_path_strs; - std::vector search_paths; - for (const auto& path : options.include_paths) { - include_path_strs.push_back(path.string()); - search_paths.push_back(include_path_strs.back().c_str()); - } - session_desc.searchPaths = search_paths.data(); - session_desc.searchPathCount = static_cast(search_paths.size()); - - // Store macro strings to keep them alive - std::vector> macro_strs; - std::vector macros; - for (const auto& [name, value] : options.defines) { - macro_strs.emplace_back(name, value); - macros.push_back({macro_strs.back().first.c_str(), macro_strs.back().second.c_str()}); - } - session_desc.preprocessorMacros = macros.data(); - session_desc.preprocessorMacroCount = static_cast(macros.size()); - - // Create session - Slang::ComPtr session; - if (SLANG_FAILED(global_session->createSession(session_desc, session.writeRef()))) { - result.error_message = "Failed to create Slang session"; - return result; - } - - // Load module - Slang::ComPtr diagnostics_blob; - slang::IModule* module = session->loadModuleFromSourceString( - filename.c_str(), - filename.c_str(), - source.c_str(), - diagnostics_blob.writeRef() - ); - - if (diagnostics_blob) { - result.error_message = static_cast(diagnostics_blob->getBufferPointer()); - } - - if (!module) { - if (result.error_message.empty()) { - result.error_message = "Failed to load shader module"; - } - return result; - } - - // Find entry point - Slang::ComPtr entry_point; - SlangStage slang_stage = options.stage ? to_slang_stage(*options.stage) : SLANG_STAGE_NONE; - - SlangResult find_result = module->findEntryPointByName(options.entry_point.c_str(), entry_point.writeRef()); - if (SLANG_FAILED(find_result)) { - // Try to find and check entry point with stage - if (options.stage) { - find_result = module->findAndCheckEntryPoint( - options.entry_point.c_str(), - slang_stage, - entry_point.writeRef(), - diagnostics_blob.writeRef() - ); - if (SLANG_FAILED(find_result)) { - result.error_message = "Entry point '" + options.entry_point + "' not found"; - if (diagnostics_blob) { - result.error_message += ": "; - result.error_message += static_cast(diagnostics_blob->getBufferPointer()); - } - return result; - } - } else { - result.error_message = "Entry point '" + options.entry_point + "' not found"; - return result; - } - } - - // Create composite program - std::vector components = {module, entry_point.get()}; - Slang::ComPtr program; - - if (SLANG_FAILED(session->createCompositeComponentType( - components.data(), - static_cast(components.size()), - program.writeRef(), - diagnostics_blob.writeRef() - ))) { - result.error_message = "Failed to create composite component type"; - if (diagnostics_blob) { - result.error_message += ": "; - result.error_message += static_cast(diagnostics_blob->getBufferPointer()); - } - return result; - } - - // Link program - Slang::ComPtr linked_program; - if (SLANG_FAILED(program->link(linked_program.writeRef(), diagnostics_blob.writeRef()))) { - result.error_message = "Failed to link shader program"; - if (diagnostics_blob) { - result.error_message += ": "; - result.error_message += static_cast(diagnostics_blob->getBufferPointer()); - } - return result; - } - - // Generate SPIR-V code - if (options.emit_spirv) { - Slang::ComPtr spirv_blob; - if (SLANG_FAILED(linked_program->getEntryPointCode( - 0, // entry point index - 0, // target index - spirv_blob.writeRef(), - diagnostics_blob.writeRef() - ))) { - result.error_message = "Failed to generate SPIR-V code"; - if (diagnostics_blob) { - result.error_message += ": "; - result.error_message += static_cast(diagnostics_blob->getBufferPointer()); - } - return result; - } - - // Copy SPIR-V data - const uint32_t* spirv_data = static_cast(spirv_blob->getBufferPointer()); - size_t spirv_size = spirv_blob->getBufferSize() / sizeof(uint32_t); - result.spirv.assign(spirv_data, spirv_data + spirv_size); - } - - // Generate reflection data - if (options.emit_reflection) { - result.reflection_json = generate_reflection_json(linked_program.get(), options.entry_point); - } - - result.success = true; - return result; - } - - std::string generate_reflection_json(slang::IComponentType* program, const std::string& entry_point_name) { - slang::ProgramLayout* layout = program->getLayout(); - if (!layout) { - return "{}"; - } - - nlohmann::json root; - root["entryPoint"] = entry_point_name; - - // Get parameter count - unsigned param_count = layout->getParameterCount(); - - // Uniform Buffers - nlohmann::json uniform_buffers = nlohmann::json::array(); - - for (unsigned i = 0; i < param_count; ++i) { - slang::VariableLayoutReflection* param = layout->getParameterByIndex(i); - if (!param) continue; - - slang::TypeLayoutReflection* type_layout = param->getTypeLayout(); - if (!type_layout) continue; - - // Check if this is a Uniform Buffer - slang::TypeReflection::Kind kind = type_layout->getKind(); - if (kind == slang::TypeReflection::Kind::ConstantBuffer || - kind == slang::TypeReflection::Kind::ParameterBlock) { - - nlohmann::json ub; - ub["name"] = param->getName() ? param->getName() : "unnamed"; - ub["set"] = param->getBindingSpace(); - ub["binding"] = param->getBindingIndex(); - - // Calculate size with fallback - size_t ub_size = type_layout->getSize(); - if (ub_size == 0) { - // Try to get size from element type layout - slang::TypeLayoutReflection* element_type = type_layout->getElementTypeLayout(); - if (element_type) { - ub_size = element_type->getSize(); - } - } - if (ub_size == 0) { - // Fallback: calculate size from members (with STD140 padding) - slang::TypeLayoutReflection* element_type = type_layout->getElementTypeLayout(); - if (element_type) { - int field_count = element_type->getFieldCount(); - size_t max_offset = 0; - for (int j = 0; j < field_count; ++j) { - slang::VariableLayoutReflection* field = element_type->getFieldByIndex(static_cast(j)); - if (!field) continue; - size_t offset = field->getOffset(); - size_t size = field->getTypeLayout()->getSize(); - // Round up to 16-byte alignment for struct (STD140 rule) - size_t field_end = offset + size; - if (field_end > max_offset) { - max_offset = field_end; - } - } - // Round up to multiple of 16 for uniform buffer alignment - ub_size = (max_offset + 15) & ~15ULL; - } - } - ub["size"] = ub_size; - - // Get members - nlohmann::json members = nlohmann::json::array(); - slang::TypeLayoutReflection* element_type = type_layout->getElementTypeLayout(); - if (element_type) { - int field_count = element_type->getFieldCount(); - for (int j = 0; j < field_count; ++j) { - slang::VariableLayoutReflection* field = element_type->getFieldByIndex(static_cast(j)); - if (!field) continue; - - nlohmann::json member; - member["name"] = field->getName() ? field->getName() : "unnamed"; - member["type"] = get_type_name(field->getTypeLayout()); - member["offset"] = field->getOffset(); - member["size"] = field->getTypeLayout()->getSize(); - members.push_back(member); - } - } - ub["members"] = members; - uniform_buffers.push_back(ub); - } - } - root["uniformBuffers"] = uniform_buffers; - - // Samplers - nlohmann::json samplers = nlohmann::json::array(); - - for (unsigned i = 0; i < param_count; ++i) { - slang::VariableLayoutReflection* param = layout->getParameterByIndex(i); - if (!param) continue; - - slang::TypeLayoutReflection* type_layout = param->getTypeLayout(); - if (!type_layout) continue; - - slang::TypeReflection::Kind kind = type_layout->getKind(); - if (kind == slang::TypeReflection::Kind::Resource) { - // Get resource shape using SlangResourceShape - SlangResourceShape shape = type_layout->getResourceShape(); - SlangResourceShape base_shape = static_cast(shape & SLANG_RESOURCE_BASE_SHAPE_MASK); - - if (base_shape == SLANG_TEXTURE_1D || - base_shape == SLANG_TEXTURE_2D || - base_shape == SLANG_TEXTURE_3D || - base_shape == SLANG_TEXTURE_CUBE) { - - nlohmann::json sampler; - sampler["name"] = param->getName() ? param->getName() : "unnamed"; - sampler["set"] = param->getBindingSpace(); - sampler["binding"] = param->getBindingIndex(); - sampler["dimension"] = get_texture_dimension(base_shape); - samplers.push_back(sampler); - } - } - } - root["samplers"] = samplers; - - // Debug: Print all parameters for analysis - for (unsigned i = 0; i < param_count; ++i) { - slang::VariableLayoutReflection* param = layout->getParameterByIndex(i); - if (!param) continue; - - slang::TypeLayoutReflection* type_layout = param->getTypeLayout(); - if (!type_layout) continue; - - slang::TypeReflection::Kind kind = type_layout->getKind(); - slang::TypeReflection* type = type_layout->getType(); - const char* param_name = param->getName(); - const char* type_name = type ? type->getName() : "null"; - unsigned binding_space = param->getBindingSpace(); - unsigned binding_index = param->getBindingIndex(); - - // Debug output - can be enabled for troubleshooting - // printf("DEBUG: param[%u] name=%s type=%s kind=%d space=%u index=%u\n", - // i, param_name ? param_name : "null", type_name, (int)kind, binding_space, binding_index); - } - - // Push Constants - try to find parameters that look like push constants - nlohmann::json push_constants = nlohmann::json::array(); - - for (unsigned i = 0; i < param_count; ++i) { - slang::VariableLayoutReflection* param = layout->getParameterByIndex(i); - if (!param) continue; - - slang::TypeLayoutReflection* type_layout = param->getTypeLayout(); - if (!type_layout) continue; - - slang::TypeReflection::Kind kind = type_layout->getKind(); - slang::TypeReflection* type = type_layout->getType(); - - // Method 1: Check for ConstantBuffer with struct element (likely push constant) - if (kind == slang::TypeReflection::Kind::ConstantBuffer) { - slang::TypeLayoutReflection* element_type = type_layout->getElementTypeLayout(); - if (element_type) { - slang::TypeReflection* element_type_ref = element_type->getType(); - if (element_type_ref && element_type_ref->getKind() == slang::TypeReflection::Kind::Struct) { - // Check if this could be a push constant - // Push constants typically have: - // - No binding index (or very small) - // - Element type is a struct - // - Name might contain "pc" or "push" or match the expected struct name - - const char* type_name = type ? type->getName() : ""; - const char* param_name = param->getName(); - - // Check if name contains "pc" or "push" or matches "PushConstants" - bool is_likely_push_constant = false; - if (param_name && (strstr(param_name, "pc") || strstr(param_name, "push") || strstr(param_name, "PushConstant"))) { - is_likely_push_constant = true; - } - if (type_name && (strstr(type_name, "pc") || strstr(type_name, "push") || strstr(type_name, "PushConstant"))) { - is_likely_push_constant = true; - } - - // Also check if binding space is 0 (no descriptor set assigned) - // and this is the only ConstantBuffer without a proper binding - unsigned binding_space = param->getBindingSpace(); - unsigned binding_index = param->getBindingIndex(); - - // If binding space is 0 but binding index is 0, and there's a struct element, - // this might be a push constant (descriptors usually have higher binding indices) - if (binding_space == 0 && binding_index == 0) { - // This could be a push constant - let's also check it's NOT in uniform buffers - // by checking if we've already processed it as a regular uniform buffer - is_likely_push_constant = true; - } - - if (is_likely_push_constant) { - nlohmann::json pc; - pc["name"] = param_name ? param_name : (type_name ? type_name : "PushConstants"); - - // Calculate size - size_t pc_size = type_layout->getSize(); - if (pc_size == 0) { - pc_size = element_type->getSize(); - } - if (pc_size == 0) { - // Fallback: calculate size from members - int field_count = element_type->getFieldCount(); - size_t max_offset = 0; - for (int j = 0; j < field_count; ++j) { - slang::VariableLayoutReflection* field = element_type->getFieldByIndex(static_cast(j)); - if (!field) continue; - size_t offset = field->getOffset(); - size_t size = field->getTypeLayout()->getSize(); - size_t field_end = offset + size; - if (field_end > max_offset) { - max_offset = field_end; - } - } - pc_size = (max_offset + 15) & ~15ULL; - } - pc["size"] = pc_size; - - // Get members - nlohmann::json members = nlohmann::json::array(); - int field_count = element_type->getFieldCount(); - for (int j = 0; j < field_count; ++j) { - slang::VariableLayoutReflection* field = element_type->getFieldByIndex(static_cast(j)); - if (!field) continue; - - nlohmann::json member; - member["name"] = field->getName() ? field->getName() : "unnamed"; - member["type"] = get_type_name(field->getTypeLayout()); - member["offset"] = field->getOffset(); - member["size"] = field->getTypeLayout()->getSize(); - members.push_back(member); - } - pc["members"] = members; - push_constants.push_back(pc); - } - } - } - } - } - root["pushConstants"] = push_constants; - - return root.dump(2); - } - - const char* get_type_name(slang::TypeLayoutReflection* type_layout) { - if (!type_layout) return "unknown"; - - slang::TypeReflection* type = type_layout->getType(); - if (!type) return "unknown"; - - switch (type->getKind()) { - case slang::TypeReflection::Kind::Scalar: { - switch (type->getScalarType()) { - case slang::TypeReflection::ScalarType::Float32: return "float"; - case slang::TypeReflection::ScalarType::Int32: return "int"; - case slang::TypeReflection::ScalarType::UInt32: return "uint"; - case slang::TypeReflection::ScalarType::Bool: return "bool"; - default: return "scalar"; - } - } - case slang::TypeReflection::Kind::Vector: { - unsigned count = type->getElementCount(); - switch (type->getScalarType()) { - case slang::TypeReflection::ScalarType::Float32: - switch (count) { - case 2: return "float2"; - case 3: return "float3"; - case 4: return "float4"; - default: return "floatN"; - } - case slang::TypeReflection::ScalarType::Int32: - switch (count) { - case 2: return "int2"; - case 3: return "int3"; - case 4: return "int4"; - default: return "intN"; - } - case slang::TypeReflection::ScalarType::UInt32: - switch (count) { - case 2: return "uint2"; - case 3: return "uint3"; - case 4: return "uint4"; - default: return "uintN"; - } - default: return "vector"; - } - } - case slang::TypeReflection::Kind::Matrix: { - unsigned rows = type->getRowCount(); - unsigned cols = type->getColumnCount(); - if (rows == 4 && cols == 4) return "float4x4"; - if (rows == 3 && cols == 3) return "float3x3"; - if (rows == 2 && cols == 2) return "float2x2"; - return "matrix"; - } - case slang::TypeReflection::Kind::Struct: - return type->getName() ? type->getName() : "struct"; - default: - return "unknown"; - } - } - - const char* get_texture_dimension(SlangResourceShape base_shape) { - switch (base_shape) { - case SLANG_TEXTURE_1D: return "1D"; - case SLANG_TEXTURE_2D: return "2D"; - case SLANG_TEXTURE_3D: return "3D"; - case SLANG_TEXTURE_CUBE: return "Cube"; - default: return "2D"; - } - } - - // 检查是否存在入口函数 - std::optional has_entry_point( - const std::string& source, - const std::string& filename, - const compile_options& options - ) { - auto entry_points = get_entry_points(source, filename, options); - return !entry_points.empty(); - } - - // 获取所有入口函数 - std::vector get_entry_points( - const std::string& source, - const std::string& filename, - const compile_options& options - ) { - std::vector result; - - if (!global_session) { - return result; - } - - // 创建 session - slang::SessionDesc session_desc = {}; - slang::TargetDesc target_desc = {}; - target_desc.format = SLANG_SPIRV; - target_desc.profile = global_session->findProfile("glsl_450"); - - session_desc.targets = &target_desc; - session_desc.targetCount = 1; - - std::vector include_path_strs; - std::vector search_paths; - for (const auto& path : options.include_paths) { - include_path_strs.push_back(path.string()); - search_paths.push_back(include_path_strs.back().c_str()); - } - session_desc.searchPaths = search_paths.data(); - session_desc.searchPathCount = static_cast(search_paths.size()); - - std::vector> macro_strs; - std::vector macros; - for (const auto& [name, value] : options.defines) { - macro_strs.emplace_back(name, value); - macros.push_back({macro_strs.back().first.c_str(), macro_strs.back().second.c_str()}); - } - session_desc.preprocessorMacros = macros.data(); - session_desc.preprocessorMacroCount = static_cast(macros.size()); - - Slang::ComPtr session; - if (SLANG_FAILED(global_session->createSession(session_desc, session.writeRef()))) { - return result; - } - - // 加载模块 - Slang::ComPtr diagnostics_blob; - slang::IModule* module = session->loadModuleFromSourceString( - filename.c_str(), - filename.c_str(), - source.c_str(), - diagnostics_blob.writeRef() - ); - - if (!module) { - return result; - } - - // 获取入口点列表 - SlangInt32 entry_point_count = module->getDefinedEntryPointCount(); - - // 常见入口点名称模式 - const char* common_names[] = {"main", "vertexMain", "fragmentMain", "computeMain", - "vertexShader", "fragmentShader", "computeShader"}; - - const SlangStage stages[] = { - SLANG_STAGE_VERTEX, - SLANG_STAGE_FRAGMENT, - SLANG_STAGE_COMPUTE, - SLANG_STAGE_GEOMETRY, - SLANG_STAGE_HULL, - SLANG_STAGE_DOMAIN - }; - - // 尝试每个可能的入口点名称和阶段的组合 - for (const char* name : common_names) { - for (SlangStage stage : stages) { - Slang::ComPtr checked_entry; - SlangResult find_result = module->findAndCheckEntryPoint( - name, - stage, - checked_entry.writeRef(), - diagnostics_blob.writeRef() - ); - - if (SLANG_FAILED(find_result) || !checked_entry) { - continue; - } - - // 验证并编译这个入口点来获取名称 - std::vector comps = {module, checked_entry.get()}; - Slang::ComPtr composite; - - if (SLANG_FAILED(session->createCompositeComponentType( - comps.data(), - static_cast(comps.size()), - composite.writeRef(), - diagnostics_blob.writeRef() - ))) { - continue; - } - - Slang::ComPtr linked; - if (SLANG_FAILED(composite->link(linked.writeRef(), diagnostics_blob.writeRef()))) { - continue; - } - - // 生成代码来触发 layout 生成 - Slang::ComPtr spirv_blob; - if (SLANG_FAILED(linked->getEntryPointCode( - 0, 0, spirv_blob.writeRef(), diagnostics_blob.writeRef()))) { - continue; - } - - // 获取 layout - slang::ProgramLayout* layout = linked->getLayout(); - if (!layout || layout->getEntryPointCount() == 0) { - continue; - } - - slang::EntryPointReflection* ep_ref = layout->getEntryPointByIndex(0); - if (!ep_ref) { - continue; - } - - const char* entry_name = ep_ref->getName(); - if (entry_name && entry_name[0] != '\0') { - // 检查是否已经添加 - bool exists = false; - for (const auto& existing : result) { - if (existing == entry_name) { - exists = true; - break; - } - } - if (!exists) { - result.push_back(entry_name); - } - } - } - } - - return result; - } - - // 获取所有入口函数及其阶段 - std::vector get_entry_points_with_stages( - const std::string& source, - const std::string& filename, - const compile_options& options - ) { - std::vector result; - - if (!global_session) { - return result; - } - - // 创建 session - slang::SessionDesc session_desc = {}; - slang::TargetDesc target_desc = {}; - target_desc.format = SLANG_SPIRV; - target_desc.profile = global_session->findProfile("glsl_450"); - - session_desc.targets = &target_desc; - session_desc.targetCount = 1; - - std::vector include_path_strs; - std::vector search_paths; - for (const auto& path : options.include_paths) { - include_path_strs.push_back(path.string()); - search_paths.push_back(include_path_strs.back().c_str()); - } - session_desc.searchPaths = search_paths.data(); - session_desc.searchPathCount = static_cast(search_paths.size()); - - std::vector> macro_strs; - std::vector macros; - for (const auto& [name, value] : options.defines) { - macro_strs.emplace_back(name, value); - macros.push_back({macro_strs.back().first.c_str(), macro_strs.back().second.c_str()}); - } - session_desc.preprocessorMacros = macros.data(); - session_desc.preprocessorMacroCount = static_cast(macros.size()); - - Slang::ComPtr session; - if (SLANG_FAILED(global_session->createSession(session_desc, session.writeRef()))) { - return result; - } - - // 加载模块 - Slang::ComPtr diagnostics_blob; - slang::IModule* module = session->loadModuleFromSourceString( - filename.c_str(), - filename.c_str(), - source.c_str(), - diagnostics_blob.writeRef() - ); - - if (!module) { - return result; - } - - // 获取所有定义的入口点数量 - SlangInt32 entry_point_count = module->getDefinedEntryPointCount(); - - if (entry_point_count == 0) { - return result; - } - - // 常见入口点名称模式 - const char* common_names[] = {"main", "vertexMain", "fragmentMain", "computeMain", - "vertexShader", "fragmentShader", "computeShader"}; - - const SlangStage stages[] = { - SLANG_STAGE_VERTEX, - SLANG_STAGE_FRAGMENT, - SLANG_STAGE_COMPUTE, - SLANG_STAGE_GEOMETRY, - SLANG_STAGE_HULL, - SLANG_STAGE_DOMAIN - }; - - // 尝试每个可能的入口点名称和阶段的组合 - for (const char* name : common_names) { - for (SlangStage stage : stages) { - // 如果已经找到所有入口点,提前退出 - if (static_cast(result.size()) >= entry_point_count) { - break; - } - - Slang::ComPtr checked_entry; - SlangResult find_result = module->findAndCheckEntryPoint( - name, - stage, - checked_entry.writeRef(), - diagnostics_blob.writeRef() - ); - - if (SLANG_FAILED(find_result) || !checked_entry) { - continue; - } - - // 验证并编译这个入口点来获取名称 - std::vector comps = {module, checked_entry.get()}; - Slang::ComPtr composite; - - if (SLANG_FAILED(session->createCompositeComponentType( - comps.data(), - static_cast(comps.size()), - composite.writeRef(), - diagnostics_blob.writeRef() - ))) { - continue; - } - - Slang::ComPtr linked; - if (SLANG_FAILED(composite->link(linked.writeRef(), diagnostics_blob.writeRef()))) { - continue; - } - - // 生成代码来触发 layout 生成 - Slang::ComPtr spirv_blob; - if (SLANG_FAILED(linked->getEntryPointCode( - 0, 0, spirv_blob.writeRef(), diagnostics_blob.writeRef()))) { - continue; - } - - // 获取 layout - slang::ProgramLayout* layout = linked->getLayout(); - if (!layout || layout->getEntryPointCount() == 0) { - continue; - } - - slang::EntryPointReflection* ep_ref = layout->getEntryPointByIndex(0); - if (!ep_ref) { - continue; - } - - const char* entry_name = ep_ref->getName(); - SlangStage slang_stage = ep_ref->getStage(); - - if (entry_name && entry_name[0] != '\0') { - // 检查是否已经添加 - bool exists = false; - for (const auto& existing : result) { - if (existing.name == entry_name) { - exists = true; - break; - } - } - - if (!exists) { - auto shader_stage_opt = slang_stage_to_shader_stage(slang_stage); - if (shader_stage_opt) { - entry_point_info info; - info.name = entry_name; - info.stage = *shader_stage_opt; - result.push_back(info); - } - } - } - } - } - - return result; - } - - // 编译所有入口点 - std::vector compile_all_entries( - const std::string& source, - const std::string& filename, - const compile_options& options - ) { - std::vector results; - - // 获取所有入口点及其阶段 - auto entry_points = get_entry_points_with_stages(source, filename, options); - - for (const auto& ep_info : entry_points) { - compile_options ep_options = options; - ep_options.entry_point = ep_info.name; - ep_options.stage = ep_info.stage; - - compile_result result = compile(source, filename, ep_options); - result.entry_point = ep_info.name; - result.stage = ep_info.stage; - results.push_back(result); - } - - return results; - } -}; - -// ============================================================================ -// shader_compiler 实现 -// ============================================================================ - -shader_compiler::shader_compiler() - : impl_(std::make_unique()) { -} - -shader_compiler::~shader_compiler() = default; - -shader_compiler::shader_compiler(shader_compiler&&) noexcept = default; -shader_compiler& shader_compiler::operator=(shader_compiler&&) noexcept = default; - -compile_result shader_compiler::compile_file( - const std::filesystem::path& path, - const compile_options& options -) { - compile_result result; - - // 读取文件内容 - std::ifstream file(path); +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; } - - std::ostringstream ss; - ss << file.rdbuf(); - std::string source = ss.str(); - - // 添加文件所在目录到包含路径 - compile_options modified_options = options; - modified_options.include_paths.insert( - modified_options.include_paths.begin(), - path.parent_path() - ); - - return impl_->compile(source, path.filename().string(), modified_options); -} - -compile_result shader_compiler::compile_source( - const std::string& source, - const std::string& filename, - const compile_options& options -) { - return impl_->compile(source, filename, options); -} - -std::optional shader_compiler::generate_reflection( - const std::filesystem::path& path, - const compile_options& options -) { - compile_options reflection_options = options; - reflection_options.emit_spirv = false; - reflection_options.emit_reflection = true; - - auto result = compile_file(path, reflection_options); - if (result.success) { - return result.reflection_json; + + 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; } - return std::nullopt; -} - -std::optional shader_compiler::has_entry_point( - const std::filesystem::path& path, - const compile_options& options -) { - // 首先检查文件是否存在 - if (!std::filesystem::exists(path)) { - return std::nullopt; // 无法检查 - } - - // 读取文件内容 - std::ifstream file(path); + + std::vector spirv(file_size / sizeof(uint32_t)); + file.read(reinterpret_cast(spirv.data()), file_size); + if (!file) { - return std::nullopt; // 无法检查 + result.error_message = "Failed to read file: " + path.string(); + return result; } - - std::ostringstream ss; - ss << file.rdbuf(); - std::string source = ss.str(); - - return impl_->has_entry_point(source, path.filename().string(), options); + + return reflect_spirv(spirv, path.filename().string()); } -std::vector shader_compiler::get_entry_points( - const std::filesystem::path& path, - const compile_options& options +reflection_result spirv_reflector::reflect_spirv( + const std::vector& spirv, + const std::string& filename ) { - // 首先检查文件是否存在 - if (!std::filesystem::exists(path)) { - return {}; + 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(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(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(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(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(); } - - // 读取文件内容 - std::ifstream file(path); - if (!file) { - return {}; - } - - std::ostringstream ss; - ss << file.rdbuf(); - std::string source = ss.str(); - - return impl_->get_entry_points(source, path.filename().string(), options); + + return result; } -std::vector shader_compiler::get_entry_points_with_stages( - const std::filesystem::path& path, - const compile_options& options +std::vector spirv_reflector::reflect_directory( + const std::filesystem::path& dir ) { - // 首先检查文件是否存在 - if (!std::filesystem::exists(path)) { - return {}; + std::vector results; + + if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) { + return results; } - - // 读取文件内容 - std::ifstream file(path); - if (!file) { - return {}; + + 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); + } } - - std::ostringstream ss; - ss << file.rdbuf(); - std::string source = ss.str(); - - return impl_->get_entry_points_with_stages(source, path.filename().string(), options); + + return results; } -std::vector shader_compiler::compile_file_all_entries( - const std::filesystem::path& path, - const compile_options& options -) { - // 首先检查文件是否存在 - if (!std::filesystem::exists(path)) { - return {}; +// ============================================================================ +// 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); } - - // 读取文件内容 - std::ifstream file(path); - if (!file) { - return {}; + 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); } - - std::ostringstream ss; - ss << file.rdbuf(); - std::string source = ss.str(); - - // 添加文件所在目录到包含路径 - compile_options modified_options = options; - modified_options.include_paths.insert( - modified_options.include_paths.begin(), - path.parent_path() - ); - - return impl_->compile_all_entries(source, path.filename().string(), modified_options); + 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); } -bool shader_compiler::is_available() const noexcept { - return impl_ && impl_->global_session; -} - -std::string shader_compiler::get_version() const { - if (!impl_ || !impl_->global_session) { - return "unavailable"; +std::string reflections_to_json(const std::vector& 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))); } - // Slang 没有直接的版本 API,返回固定字符串 - return "Slang Shader Compiler"; + + return root.dump(2); } } // namespace mirai::tools diff --git a/tools/shader_compile/compiler.hpp b/tools/shader_compile/compiler.hpp index 1ee9529..95c0a39 100644 --- a/tools/shader_compile/compiler.hpp +++ b/tools/shader_compile/compiler.hpp @@ -1,13 +1,12 @@ // tools/shader_compile/compiler.hpp -// MIRAI 着色器编译器封装 +// MIRAI 着色器反射工具 +// 使用 spirv-cross 从 SPIR-V 提取反射信息 #pragma once #include #include -#include #include -#include #include #include @@ -23,6 +22,7 @@ enum class shader_stage { geometry, tessellation_control, tessellation_evaluation, + unknown }; /** @@ -36,157 +36,138 @@ enum class shader_stage { [[nodiscard]] const char* shader_stage_to_string(shader_stage stage); /** - * @brief 将 Slang 阶段转换为 shader_stage + * @brief 从文件扩展名推断着色器阶段 */ -[[nodiscard]] std::optional slang_stage_to_shader_stage(SlangStage stage); +[[nodiscard]] shader_stage stage_from_extension(const std::string& ext); /** - * @brief 编译结果 + * @brief Uniform Buffer 成员信息 */ -struct compile_result { +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 members; +}; + +/** + * @brief Push Constant 信息 + */ +struct push_constant_info { + std::string name; + uint32_t size{0}; + std::vector 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_buffers; + std::vector push_constants; + std::vector samplers; + std::vector vertex_inputs; + std::vector fragment_outputs; +}; + +/** + * @brief SPIR-V 反射结果 + */ +struct reflection_result { bool success{false}; - std::string entry_point; // 入口点名称 - shader_stage stage; // 着色器阶段 - std::vector spirv; + shader_reflection reflection; std::string reflection_json; std::string error_message; - std::vector dependencies; }; /** - * @brief 编译选项 - */ -struct compile_options { - std::string entry_point{"main"}; - std::optional stage; - std::vector include_paths; - std::vector> defines; - bool generate_debug_info{false}; - bool optimize{true}; - bool emit_spirv{true}; - bool emit_reflection{false}; -}; - -/** - * @brief 入口点信息(包含名称和阶段) - */ -struct entry_point_info { - std::string name; - shader_stage stage; -}; - -/** - * @brief 着色器编译器 + * @brief SPIR-V 反射器 * - * 封装 Slang SDK,用于将 .slang 文件编译为 SPIR-V + * 使用 spirv-cross 从 SPIR-V 字节码提取反射信息 */ -class shader_compiler { +class spirv_reflector { public: - shader_compiler(); - ~shader_compiler(); + spirv_reflector() = default; + ~spirv_reflector() = default; // 禁止拷贝 - shader_compiler(const shader_compiler&) = delete; - shader_compiler& operator=(const shader_compiler&) = delete; + spirv_reflector(const spirv_reflector&) = delete; + spirv_reflector& operator=(const spirv_reflector&) = delete; // 允许移动 - shader_compiler(shader_compiler&&) noexcept; - shader_compiler& operator=(shader_compiler&&) noexcept; + spirv_reflector(spirv_reflector&&) noexcept = default; + spirv_reflector& operator=(spirv_reflector&&) noexcept = default; /** - * @brief 编译着色器文件 - * @param path 输入文件路径 - * @param options 编译选项 - * @return 编译结果 + * @brief 从 SPIR-V 文件提取反射信息 + * @param path SPIR-V 文件路径 + * @return 反射结果 */ - [[nodiscard]] compile_result compile_file( - const std::filesystem::path& path, - const compile_options& options + [[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& spirv, + const std::string& filename = "" ); /** - * @brief 编译着色器源码 - * @param source 着色器源码 - * @param filename 虚拟文件名(用于错误报告) - * @param options 编译选项 - * @return 编译结果 + * @brief 从目录中的所有 SPIR-V 文件提取反射信息 + * @param dir 目录路径 + * @return 反射结果列表 */ - [[nodiscard]] compile_result compile_source( - const std::string& source, - const std::string& filename, - const compile_options& options + [[nodiscard]] std::vector reflect_directory( + const std::filesystem::path& dir ); - - /** - * @brief 仅生成反射数据 - * @param path 输入文件路径 - * @param options 编译选项 - * @return 反射 JSON 字符串,失败返回 nullopt - */ - [[nodiscard]] std::optional generate_reflection( - const std::filesystem::path& path, - const compile_options& options - ); - - /** - * @brief 检查着色器文件是否存在入口函数 - * @param path 输入文件路径 - * @param options 编译选项 - * @return std::nullopt 表示无法检查, - * true 表示存在入口函数, - * false 表示不存在入口函数 - */ - [[nodiscard]] std::optional has_entry_point( - const std::filesystem::path& path, - const compile_options& options - ); - - /** - * @brief 获取模块中的所有入口函数 - * @param path 输入文件路径 - * @param options 编译选项 - * @return 入口函数名称列表 - */ - [[nodiscard]] std::vector get_entry_points( - const std::filesystem::path& path, - const compile_options& options - ); - - /** - * @brief 获取模块中的所有入口函数及其阶段 - * @param path 输入文件路径 - * @param options 编译选项 - * @return 入口点信息列表 - */ - [[nodiscard]] std::vector get_entry_points_with_stages( - const std::filesystem::path& path, - const compile_options& options - ); - - /** - * @brief 编译着色器文件(自动检测所有入口点) - * @param path 输入文件路径 - * @param options 编译选项 - * @return 编译结果列表(每个入口点一个结果) - */ - [[nodiscard]] std::vector compile_file_all_entries( - const std::filesystem::path& path, - const compile_options& options - ); - - /** - * @brief 检查编译器是否可用 - */ - [[nodiscard]] bool is_available() const noexcept; - - /** - * @brief 获取 Slang 版本信息 - */ - [[nodiscard]] std::string get_version() const; - -private: - struct impl; - std::unique_ptr impl_; }; +/** + * @brief 将反射数据转换为 JSON 字符串 + */ +[[nodiscard]] std::string reflection_to_json(const shader_reflection& reflection); + +/** + * @brief 将多个反射数据合并为一个 JSON 字符串 + */ +[[nodiscard]] std::string reflections_to_json(const std::vector& reflections); + } // namespace mirai::tools diff --git a/tools/shader_compile/main.cpp b/tools/shader_compile/main.cpp index a249586..0454af7 100644 --- a/tools/shader_compile/main.cpp +++ b/tools/shader_compile/main.cpp @@ -1,6 +1,6 @@ // tools/shader_compile/main.cpp -// MIRAI 着色器编译器命令行入口 -// 自动检测所有 [shader("xxx")] 入口点并编译 +// MIRAI 着色器反射工具命令行入口 +// 从 SPIR-V 文件提取反射信息并生成绑定代码 #include "compiler.hpp" @@ -13,47 +13,46 @@ namespace { void print_usage(const char* program_name) { std::cout << R"( -MIRAI Shader Compiler - Auto Mode +MIRAI Shader Reflection Tool -Usage: )" << program_name << R"( [options] +Usage: )" << program_name << R"( [options] Options: - -o, --output Output directory for compiled shaders - -I, --include Add include search path - -D, --define Define preprocessor macro (e.g., -DDEBUG or -DVALUE=1) - --prefix Prefix for output file names (default: shader name) - -g Generate debug info - -O0 Disable optimization - --list Only list entry points, don't compile + --dir Directory containing SPIR-V files + --file Single SPIR-V file to process + --output Output file path (header or JSON) + --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"( shader.slang -o ./compiled_shaders - )" << program_name << R"( shader.slang -o ./shaders --prefix myshader - )" << program_name << R"( shader.slang --list + )" << 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 Format: - Automatically compiles all entry points found via [shader("xxx")] attributes. - Output files: ...spv - ...reflect.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 Compiler v2.0.0 (Auto Mode)\n"; - std::cout << "Based on Slang Shader Language\n"; + 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_path; - std::filesystem::path output_dir; - std::string prefix; - mirai::tools::compile_options options; + 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 list_only = false; }; bool parse_args(int argc, char* argv[], command_line_args& args) { @@ -68,67 +67,41 @@ bool parse_args(int argc, char* argv[], command_line_args& args) { args.show_version = true; return true; } - if (arg == "-o" || arg == "--output") { + if (arg == "--dir") { if (++i >= argc) { - std::cerr << "Error: -o requires an argument\n"; + std::cerr << "Error: --dir requires an argument\n"; return false; } - args.output_dir = argv[i]; + args.input_dir = argv[i]; } - else if (arg == "-I" || arg == "--include") { + else if (arg == "--file") { if (++i >= argc) { - std::cerr << "Error: -I requires an argument\n"; + std::cerr << "Error: --file requires an argument\n"; return false; } - args.options.include_paths.emplace_back(argv[i]); + args.input_file = argv[i]; } - else if (arg.starts_with("-I")) { - args.options.include_paths.emplace_back(arg.substr(2)); - } - else if (arg == "-D" || arg == "--define") { + else if (arg == "--output" || arg == "-o") { if (++i >= argc) { - std::cerr << "Error: -D requires an argument\n"; + std::cerr << "Error: --output requires an argument\n"; return false; } - std::string def = argv[i]; - auto eq_pos = def.find('='); - if (eq_pos != std::string::npos) { - args.options.defines.emplace_back(def.substr(0, eq_pos), def.substr(eq_pos + 1)); - } else { - args.options.defines.emplace_back(def, "1"); - } + args.output_path = argv[i]; } - else if (arg.starts_with("-D")) { - std::string def = arg.substr(2); - auto eq_pos = def.find('='); - if (eq_pos != std::string::npos) { - args.options.defines.emplace_back(def.substr(0, eq_pos), def.substr(eq_pos + 1)); - } else { - args.options.defines.emplace_back(def, "1"); - } - } - else if (arg == "--prefix") { + else if (arg == "--name") { if (++i >= argc) { - std::cerr << "Error: --prefix requires an argument\n"; + std::cerr << "Error: --name requires an argument\n"; return false; } - args.prefix = argv[i]; + args.name = argv[i]; } - else if (arg == "--list") { - args.list_only = true; + else if (arg == "--json") { + args.output_json = true; + args.output_header = false; } - else if (arg == "-g") { - args.options.generate_debug_info = true; - } - else if (arg == "-O0") { - args.options.optimize = false; - } - else if (!arg.starts_with("-")) { - if (!args.input_path.empty()) { - std::cerr << "Error: Multiple input files specified\n"; - return false; - } - args.input_path = arg; + else if (arg == "--header") { + args.output_header = true; + args.output_json = false; } else { std::cerr << "Error: Unknown option: " << arg << "\n"; @@ -139,29 +112,6 @@ bool parse_args(int argc, char* argv[], command_line_args& args) { return true; } -std::string stage_to_suffix(mirai::tools::shader_stage stage) { - switch (stage) { - case mirai::tools::shader_stage::vertex: return "vert"; - case mirai::tools::shader_stage::fragment: return "frag"; - case mirai::tools::shader_stage::compute: return "comp"; - case mirai::tools::shader_stage::geometry: return "geom"; - case mirai::tools::shader_stage::tessellation_control: return "tesc"; - case mirai::tools::shader_stage::tessellation_evaluation: return "tese"; - } - return "unknown"; -} - -bool write_spirv(const std::filesystem::path& path, const std::vector& spirv) { - std::ofstream file(path, std::ios::binary); - if (!file) { - std::cerr << "Error: Failed to open output file: " << path << "\n"; - return false; - } - - file.write(reinterpret_cast(spirv.data()), spirv.size() * sizeof(uint32_t)); - return file.good(); -} - bool write_text(const std::filesystem::path& path, const std::string& text) { std::ofstream file(path); if (!file) { @@ -173,6 +123,112 @@ bool write_text(const std::filesystem::path& path, const std::string& text) { return file.good(); } +std::vector 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 data(size); + file.read(reinterpret_cast(data.data()), size); + + return data; +} + +std::string generate_header( + const std::string& name, + const std::vector& results +) { + std::ostringstream ss; + + // Header guard + std::string guard_name = name; + for (auto& c : guard_name) { + c = static_cast(std::toupper(static_cast(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 \n"; + ss << "#include \n"; + ss << "#include \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"; + + // 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 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[]) { @@ -192,87 +248,80 @@ int main(int argc, char* argv[]) { return 0; } - if (args.input_path.empty()) { - std::cerr << "Error: No input file specified\n"; + 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; } - // 创建编译器 - mirai::tools::shader_compiler compiler; - if (!compiler.is_available()) { - std::cerr << "Error: Shader compiler is not available\n"; + if (args.output_path.empty()) { + std::cerr << "Error: No output path specified. Use --output.\n"; return 1; } - // 获取所有入口点及其阶段 - auto entry_points = compiler.get_entry_points_with_stages(args.input_path, args.options); + // Create reflector + mirai::tools::spirv_reflector reflector; + std::vector results; - if (entry_points.empty()) { - std::cout << "Warning: No entry points found in " << args.input_path << "\n"; - std::cout << " (This may be a module file with no [shader(...)] attributes)\n"; + // 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; } - // 列出入口点 - std::cout << "Found " << entry_points.size() << " entry point(s) in " << args.input_path.filename() << ":\n"; - for (const auto& ep : entry_points) { - std::cout << " - " << ep.name << " [" << mirai::tools::shader_stage_to_string(ep.stage) << "]\n"; - } - - // 如果只是列出,不编译 - if (args.list_only) { - return 0; - } - - // 确保输出目录存在 - if (args.output_dir.empty()) { - args.output_dir = "."; - } - if (!std::filesystem::exists(args.output_dir)) { - std::filesystem::create_directories(args.output_dir); - } - - // 确定前缀 - if (args.prefix.empty()) { - args.prefix = args.input_path.stem().string(); - } - - // 编译所有入口点 - args.options.emit_spirv = true; - args.options.emit_reflection = true; - - auto results = compiler.compile_file_all_entries(args.input_path, args.options); - - int success_count = 0; - int fail_count = 0; - + // Check for errors + int error_count = 0; for (const auto& result : results) { if (!result.success) { - std::cerr << "Error compiling " << result.entry_point << ": " << result.error_message << "\n"; - fail_count++; - continue; + std::cerr << "Error: " << result.error_message << "\n"; + error_count++; } - - std::string suffix = stage_to_suffix(result.stage); - std::string base_name = args.prefix + "." + result.entry_point + "." + suffix; - - // 写入 SPIR-V - std::filesystem::path spv_path = args.output_dir / (base_name + ".spv"); - if (write_spirv(spv_path, result.spirv)) { - std::cout << "Generated: " << spv_path << " (" << result.spirv.size() * 4 << " bytes)\n"; - } - - // 写入反射 JSON - std::filesystem::path reflect_path = args.output_dir / (base_name + ".reflect.json"); - if (write_text(reflect_path, result.reflection_json)) { - std::cout << "Generated: " << reflect_path << "\n"; - } - - success_count++; } - std::cout << "\nSummary: " << success_count << " succeeded, " << fail_count << " failed\n"; + // Generate output + std::string output; + + if (args.output_json) { + std::vector 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); + } - return fail_count > 0 ? 1 : 0; + // Write output + if (!write_text(args.output_path, output)) { + return 1; + } + + std::cout << "Generated: " << args.output_path << "\n"; + + int success_count = static_cast(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; } diff --git a/vcpkg.json b/vcpkg.json index 7ac4d86..5972930 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -13,8 +13,8 @@ "version>=": "3.2.28" }, { - "name": "shader-slang", - "version>=": "2025.22.1" + "name": "spirv-cross", + "version>=": "1.3.296.0" }, { "name": "nlohmann-json",