Compare commits
10 Commits
7130c595a1
...
ceba4059de
| Author | SHA1 | Date | |
|---|---|---|---|
| ceba4059de | |||
| 5d9cde18a3 | |||
| d5d74034be | |||
| a88f43adf8 | |||
| 596f503dfa | |||
| 63bc415857 | |||
| f770bc5225 | |||
| 140f5840e6 | |||
| 87e9f316a7 | |||
| 70a0a5117c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/build
|
||||
/cmake-*
|
||||
/.idea
|
||||
/src/shader/generated
|
||||
|
||||
@@ -109,18 +109,26 @@ include(Catch)
|
||||
# ================================================================================================
|
||||
# 添加子目录
|
||||
# ================================================================================================
|
||||
# 添加工具目录
|
||||
add_subdirectory(tools/shader_compile)
|
||||
|
||||
# 包含着色器编译模块 (必须在 tools/shader_compile 之后)
|
||||
include(shader_compile)
|
||||
|
||||
add_subdirectory(src/core)
|
||||
add_subdirectory(src/threading)
|
||||
add_subdirectory(src/window)
|
||||
add_subdirectory(src/resource)
|
||||
add_subdirectory(src/shader)
|
||||
add_subdirectory(src/render)
|
||||
add_subdirectory(src/widget)
|
||||
add_subdirectory(src/text)
|
||||
add_subdirectory(src/input)
|
||||
add_subdirectory(src/reactive)
|
||||
add_subdirectory(src/loop)
|
||||
add_subdirectory(src/ui)
|
||||
add_subdirectory(src/debug)
|
||||
add_subdirectory(src/demo)
|
||||
add_subdirectory(src/app)
|
||||
|
||||
# 添加测试目录
|
||||
@@ -131,6 +139,11 @@ add_subdirectory(tests)
|
||||
# ================================================================================================
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# 安装 mirai_project_options 接口库(所有其他库都依赖它)
|
||||
install(TARGETS mirai_project_options
|
||||
EXPORT mirai-targets
|
||||
)
|
||||
|
||||
install(EXPORT mirai-targets
|
||||
FILE mirai-targets.cmake
|
||||
NAMESPACE mirai::
|
||||
|
||||
@@ -78,7 +78,7 @@ function(retrieve_files_custom path extension out_files)
|
||||
|
||||
# 3. 递归查找所有匹配的文件
|
||||
file(GLOB_RECURSE found_files
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
RELATIVE ${path}
|
||||
CONFIGURE_DEPENDS ${file_patterns}
|
||||
)
|
||||
|
||||
@@ -581,7 +581,7 @@ function(simple_executable)
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC project_options)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC mirai_project_options)
|
||||
message(STATUS "创建可执行文件目标: ${PROJECT_NAME},引用路径: ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
add_os_definitions(${PROJECT_NAME})
|
||||
endfunction()
|
||||
@@ -604,13 +604,13 @@ function(simple_library library_type)
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
target_link_libraries(${PROJECT_NAME} INTERFACE project_options)
|
||||
target_link_libraries(${PROJECT_NAME} INTERFACE mirai_project_options)
|
||||
else()
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC project_options)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC mirai_project_options)
|
||||
endif()
|
||||
message(STATUS "创建库目标: ${PROJECT_NAME},类型: ${library_type},引用路径: ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
add_os_definitions(${PROJECT_NAME})
|
||||
|
||||
515
cmake/shader_compile.cmake
Normal file
515
cmake/shader_compile.cmake
Normal file
@@ -0,0 +1,515 @@
|
||||
# ============================================================================
|
||||
# MIRAI 着色器编译 CMake 模块
|
||||
# ============================================================================
|
||||
# 提供自动化 GLSL 着色器编译和代码生成功能
|
||||
# 使用 glslangValidator 或 glslc (shaderc) 编译 GLSL 到 SPIR-V
|
||||
#
|
||||
# 主要函数:
|
||||
# add_glsl_shader_library() - 创建 GLSL 着色器库目标
|
||||
# compile_glsl_shader() - 编译单个 GLSL 着色器
|
||||
#
|
||||
# 辅助函数:
|
||||
# find_python_with_jinja2() - 查找带 Jinja2 的 Python
|
||||
# find_glsl_compiler() - 查找 GLSL 编译器
|
||||
# ============================================================================
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
# ============================================================================
|
||||
# find_python_with_jinja2 - 查找带 Jinja2 模块的 Python
|
||||
# ============================================================================
|
||||
function(find_python_with_jinja2 OUT_PYTHON_EXECUTABLE)
|
||||
# 首先查找 Python
|
||||
find_package(Python3 COMPONENTS Interpreter QUIET)
|
||||
|
||||
if(NOT Python3_FOUND)
|
||||
message(FATAL_ERROR "Python3 not found. Please install Python 3.")
|
||||
endif()
|
||||
|
||||
# 检查 Jinja2 是否已安装
|
||||
execute_process(
|
||||
COMMAND ${Python3_EXECUTABLE} -c "import jinja2; print(jinja2.__version__)"
|
||||
RESULT_VARIABLE JINJA2_CHECK_RESULT
|
||||
OUTPUT_VARIABLE JINJA2_VERSION
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
if(NOT JINJA2_CHECK_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR
|
||||
"Python Jinja2 module not found.\n"
|
||||
"Please install it with: ${Python3_EXECUTABLE} -m pip install jinja2"
|
||||
)
|
||||
endif()
|
||||
|
||||
message(STATUS "Found Python with Jinja2: ${Python3_EXECUTABLE} (Jinja2 ${JINJA2_VERSION})")
|
||||
set(${OUT_PYTHON_EXECUTABLE} "${Python3_EXECUTABLE}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# ============================================================================
|
||||
# find_glsl_compiler - 查找 GLSL 编译器 (glslangValidator 或 glslc)
|
||||
# ============================================================================
|
||||
function(find_glsl_compiler OUT_COMPILER OUT_COMPILER_TYPE)
|
||||
# 首先尝试查找 glslc (shaderc)
|
||||
find_program(GLSLC_EXECUTABLE glslc
|
||||
HINTS
|
||||
$ENV{VULKAN_SDK}/Bin
|
||||
$ENV{VULKAN_SDK}/bin
|
||||
${Vulkan_GLSLC_EXECUTABLE}
|
||||
)
|
||||
|
||||
if(GLSLC_EXECUTABLE)
|
||||
message(STATUS "Found GLSL compiler: ${GLSLC_EXECUTABLE} (glslc)")
|
||||
set(${OUT_COMPILER} "${GLSLC_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(${OUT_COMPILER_TYPE} "glslc" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# 尝试查找 glslangValidator
|
||||
find_program(GLSLANG_VALIDATOR_EXECUTABLE glslangValidator
|
||||
HINTS
|
||||
$ENV{VULKAN_SDK}/Bin
|
||||
$ENV{VULKAN_SDK}/bin
|
||||
${Vulkan_GLSLANG_VALIDATOR_EXECUTABLE}
|
||||
)
|
||||
|
||||
if(GLSLANG_VALIDATOR_EXECUTABLE)
|
||||
message(STATUS "Found GLSL compiler: ${GLSLANG_VALIDATOR_EXECUTABLE} (glslangValidator)")
|
||||
set(${OUT_COMPILER} "${GLSLANG_VALIDATOR_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(${OUT_COMPILER_TYPE} "glslangValidator" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
message(FATAL_ERROR
|
||||
"No GLSL compiler found. Please install Vulkan SDK or shaderc.\n"
|
||||
"Expected: glslc or glslangValidator in PATH or VULKAN_SDK"
|
||||
)
|
||||
endfunction()
|
||||
|
||||
# ============================================================================
|
||||
# 内部变量设置
|
||||
# ============================================================================
|
||||
|
||||
# 着色器编译器目标名称
|
||||
set(MIRAI_SHADER_COMPILER_TARGET "mirai_shader_compile")
|
||||
|
||||
# 代码生成脚本路径
|
||||
set(MIRAI_SHADER_GENERATOR_SCRIPT "${CMAKE_SOURCE_DIR}/tools/generate_shader_bindings.py")
|
||||
|
||||
# 模板目录
|
||||
set(MIRAI_SHADER_TEMPLATE_DIR "${CMAKE_SOURCE_DIR}/tools/templates")
|
||||
|
||||
# ============================================================================
|
||||
# _mirai_ensure_shader_compiler - 内部函数,确保着色器编译器可用
|
||||
# ============================================================================
|
||||
function(_mirai_ensure_shader_compiler)
|
||||
# 检查编译器目标是否存在
|
||||
if(NOT TARGET ${MIRAI_SHADER_COMPILER_TARGET})
|
||||
message(FATAL_ERROR
|
||||
"Shader compiler target '${MIRAI_SHADER_COMPILER_TARGET}' not found.\n"
|
||||
"Make sure add_subdirectory(tools/shader_compile) is called before using add_glsl_shader_library()."
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# ============================================================================
|
||||
# _get_shader_stage_from_extension - 从文件扩展名获取着色器阶段
|
||||
# ============================================================================
|
||||
function(_get_shader_stage_from_extension FILE_PATH OUT_STAGE)
|
||||
get_filename_component(EXT "${FILE_PATH}" EXT)
|
||||
string(TOLOWER "${EXT}" EXT_LOWER)
|
||||
|
||||
if(EXT_LOWER STREQUAL ".vert")
|
||||
set(${OUT_STAGE} "vert" PARENT_SCOPE)
|
||||
elseif(EXT_LOWER STREQUAL ".frag")
|
||||
set(${OUT_STAGE} "frag" PARENT_SCOPE)
|
||||
elseif(EXT_LOWER STREQUAL ".comp")
|
||||
set(${OUT_STAGE} "comp" PARENT_SCOPE)
|
||||
elseif(EXT_LOWER STREQUAL ".geom")
|
||||
set(${OUT_STAGE} "geom" PARENT_SCOPE)
|
||||
elseif(EXT_LOWER STREQUAL ".tesc")
|
||||
set(${OUT_STAGE} "tesc" PARENT_SCOPE)
|
||||
elseif(EXT_LOWER STREQUAL ".tese")
|
||||
set(${OUT_STAGE} "tese" PARENT_SCOPE)
|
||||
else()
|
||||
set(${OUT_STAGE} "" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# ============================================================================
|
||||
# compile_glsl_shader - 编译单个 GLSL 着色器
|
||||
# ============================================================================
|
||||
#
|
||||
# 用法:
|
||||
# compile_glsl_shader(
|
||||
# INPUT shader.vert
|
||||
# OUTPUT shader.vert.spv
|
||||
# STAGE vert
|
||||
# DEFINES DEBUG FEATURE_X
|
||||
# INCLUDE_DIRS include/
|
||||
# )
|
||||
#
|
||||
# 参数:
|
||||
# INPUT - 输入 GLSL 文件路径(必需)
|
||||
# OUTPUT - 输出 SPIR-V 文件路径(必需)
|
||||
# STAGE - 着色器阶段 (vert/frag/comp/geom/tesc/tese)(可选,自动检测)
|
||||
# DEFINES - 预处理器定义(可选)
|
||||
# INCLUDE_DIRS - include 目录(可选)
|
||||
#
|
||||
function(compile_glsl_shader)
|
||||
cmake_parse_arguments(SHADER "" "INPUT;OUTPUT;STAGE" "DEFINES;INCLUDE_DIRS" ${ARGN})
|
||||
|
||||
# 验证必需参数
|
||||
if(NOT SHADER_INPUT)
|
||||
message(FATAL_ERROR "compile_glsl_shader: INPUT is required")
|
||||
endif()
|
||||
if(NOT SHADER_OUTPUT)
|
||||
message(FATAL_ERROR "compile_glsl_shader: OUTPUT is required")
|
||||
endif()
|
||||
|
||||
# 自动检测着色器阶段
|
||||
if(NOT SHADER_STAGE)
|
||||
_get_shader_stage_from_extension("${SHADER_INPUT}" SHADER_STAGE)
|
||||
if(NOT SHADER_STAGE)
|
||||
message(FATAL_ERROR "compile_glsl_shader: Cannot determine shader stage from extension. Please specify STAGE.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# 查找 GLSL 编译器
|
||||
find_glsl_compiler(GLSL_COMPILER GLSL_COMPILER_TYPE)
|
||||
|
||||
# 构建编译命令参数
|
||||
set(COMPILE_ARGS "")
|
||||
|
||||
if(GLSL_COMPILER_TYPE STREQUAL "glslc")
|
||||
# glslc 参数
|
||||
list(APPEND COMPILE_ARGS "-fshader-stage=${SHADER_STAGE}")
|
||||
list(APPEND COMPILE_ARGS "--target-env=vulkan1.3")
|
||||
list(APPEND COMPILE_ARGS "-o" "${SHADER_OUTPUT}")
|
||||
|
||||
# 添加 include 目录
|
||||
foreach(INC_DIR ${SHADER_INCLUDE_DIRS})
|
||||
list(APPEND COMPILE_ARGS "-I${INC_DIR}")
|
||||
endforeach()
|
||||
|
||||
# 添加宏定义
|
||||
foreach(DEF ${SHADER_DEFINES})
|
||||
list(APPEND COMPILE_ARGS "-D${DEF}")
|
||||
endforeach()
|
||||
|
||||
list(APPEND COMPILE_ARGS "${SHADER_INPUT}")
|
||||
else()
|
||||
# glslangValidator 参数
|
||||
list(APPEND COMPILE_ARGS "-V") # 生成 SPIR-V
|
||||
list(APPEND COMPILE_ARGS "--target-env" "vulkan1.3")
|
||||
list(APPEND COMPILE_ARGS "-S" "${SHADER_STAGE}")
|
||||
list(APPEND COMPILE_ARGS "-o" "${SHADER_OUTPUT}")
|
||||
|
||||
# 添加 include 目录
|
||||
foreach(INC_DIR ${SHADER_INCLUDE_DIRS})
|
||||
list(APPEND COMPILE_ARGS "-I${INC_DIR}")
|
||||
endforeach()
|
||||
|
||||
# 添加宏定义
|
||||
foreach(DEF ${SHADER_DEFINES})
|
||||
list(APPEND COMPILE_ARGS "-D${DEF}")
|
||||
endforeach()
|
||||
|
||||
list(APPEND COMPILE_ARGS "${SHADER_INPUT}")
|
||||
endif()
|
||||
|
||||
# 创建编译命令
|
||||
add_custom_command(
|
||||
OUTPUT "${SHADER_OUTPUT}"
|
||||
COMMAND ${GLSL_COMPILER} ${COMPILE_ARGS}
|
||||
DEPENDS "${SHADER_INPUT}"
|
||||
COMMENT "Compiling GLSL shader: ${SHADER_INPUT}"
|
||||
VERBATIM
|
||||
)
|
||||
endfunction()
|
||||
|
||||
# ============================================================================
|
||||
# add_glsl_shader_library - 添加 GLSL 着色器库到现有目标
|
||||
# ============================================================================
|
||||
#
|
||||
# 将 GLSL 着色器编译和代码生成附加到现有的编译目标上
|
||||
#
|
||||
# 用法:
|
||||
# # 方案1: 自动搜索着色器
|
||||
# add_glsl_shader_library(TARGET mirai_shader
|
||||
# SHADER_PATH shaders/glsl
|
||||
# INCLUDE_DIRS shaders/glsl/common
|
||||
# )
|
||||
#
|
||||
# # 方案2: 指定具体着色器文件
|
||||
# add_glsl_shader_library(TARGET mirai_shader
|
||||
# SHADERS shaders/ui/rect.vert shaders/ui/rect.frag
|
||||
# INCLUDE_DIRS shaders/common
|
||||
# )
|
||||
#
|
||||
# 参数:
|
||||
# TARGET - 现有的编译目标(必需)
|
||||
# SHADERS - 具体的着色器文件列表(可选)
|
||||
# SHADER_PATH - 着色器搜索目录,相对于 CMAKE_CURRENT_SOURCE_DIR(可选)
|
||||
# INCLUDE_DIRS - 着色器 include 路径(可选)
|
||||
# DEFINES - 预处理器定义(可选)
|
||||
# RECURSIVE - 是否递归搜索子目录(可选,默认 OFF)
|
||||
#
|
||||
function(add_glsl_shader_library)
|
||||
# 解析参数
|
||||
set(options RECURSIVE)
|
||||
set(oneValueArgs TARGET SHADER_PATH)
|
||||
set(multiValueArgs SHADERS INCLUDE_DIRS DEFINES)
|
||||
cmake_parse_arguments(SHADER "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
# 验证必需参数
|
||||
if(NOT SHADER_TARGET)
|
||||
message(FATAL_ERROR "add_glsl_shader_library: TARGET is required")
|
||||
endif()
|
||||
|
||||
# 验证目标存在
|
||||
if(NOT TARGET ${SHADER_TARGET})
|
||||
message(FATAL_ERROR "add_glsl_shader_library: Target '${SHADER_TARGET}' does not exist")
|
||||
endif()
|
||||
|
||||
# 收集着色器文件
|
||||
set(ALL_SHADERS "${SHADER_SHADERS}")
|
||||
|
||||
if(SHADER_SHADER_PATH)
|
||||
set(SHADER_EXTENSIONS "vert;frag;comp;geom;tesc;tese")
|
||||
|
||||
if(SHADER_RECURSIVE)
|
||||
foreach(EXT ${SHADER_EXTENSIONS})
|
||||
file(GLOB_RECURSE FOUND_SHADERS "${CMAKE_CURRENT_SOURCE_DIR}/${SHADER_SHADER_PATH}/*.${EXT}")
|
||||
list(APPEND ALL_SHADERS ${FOUND_SHADERS})
|
||||
endforeach()
|
||||
else()
|
||||
foreach(EXT ${SHADER_EXTENSIONS})
|
||||
file(GLOB FOUND_SHADERS "${CMAKE_CURRENT_SOURCE_DIR}/${SHADER_SHADER_PATH}/*.${EXT}")
|
||||
list(APPEND ALL_SHADERS ${FOUND_SHADERS})
|
||||
endforeach()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# 去重
|
||||
if(ALL_SHADERS)
|
||||
list(REMOVE_DUPLICATES ALL_SHADERS)
|
||||
endif()
|
||||
|
||||
# 设置输出目录
|
||||
set(SHADER_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||
set(SHADER_INTERMEDIATE_DIR "${CMAKE_CURRENT_BINARY_DIR}/shader_intermediate")
|
||||
|
||||
file(MAKE_DIRECTORY "${SHADER_OUTPUT_DIR}")
|
||||
file(MAKE_DIRECTORY "${SHADER_INTERMEDIATE_DIR}")
|
||||
|
||||
# 确保编译器可用
|
||||
_mirai_ensure_shader_compiler()
|
||||
|
||||
# 查找 Python with Jinja2
|
||||
find_python_with_jinja2(PYTHON_EXECUTABLE)
|
||||
|
||||
# 查找 GLSL 编译器
|
||||
find_glsl_compiler(GLSL_COMPILER GLSL_COMPILER_TYPE)
|
||||
|
||||
# 构建 include 路径参数
|
||||
set(INCLUDE_ARGS "")
|
||||
foreach(INC_DIR ${SHADER_INCLUDE_DIRS})
|
||||
if(IS_ABSOLUTE "${INC_DIR}")
|
||||
list(APPEND INCLUDE_ARGS "${INC_DIR}")
|
||||
else()
|
||||
list(APPEND INCLUDE_ARGS "${CMAKE_CURRENT_SOURCE_DIR}/${INC_DIR}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# 收集生成的文件
|
||||
set(ALL_GENERATED_HEADERS "")
|
||||
set(ALL_SPV_FILES "")
|
||||
|
||||
# 按着色器名称分组(去掉扩展名后相同的文件归为一组)
|
||||
set(SHADER_GROUPS "")
|
||||
foreach(SHADER_SOURCE ${ALL_SHADERS})
|
||||
get_filename_component(SHADER_NAME_WE "${SHADER_SOURCE}" NAME_WE)
|
||||
get_filename_component(SHADER_DIR "${SHADER_SOURCE}" DIRECTORY)
|
||||
|
||||
# 创建唯一的组标识符
|
||||
string(REPLACE "/" "_" DIR_SAFE "${SHADER_DIR}")
|
||||
set(GROUP_ID "${DIR_SAFE}_${SHADER_NAME_WE}")
|
||||
|
||||
# 添加到组列表
|
||||
list(APPEND SHADER_GROUPS "${GROUP_ID}")
|
||||
list(APPEND ${GROUP_ID}_SOURCES "${SHADER_SOURCE}")
|
||||
set(${GROUP_ID}_NAME "${SHADER_NAME_WE}")
|
||||
set(${GROUP_ID}_DIR "${SHADER_DIR}")
|
||||
endforeach()
|
||||
|
||||
# 去重组列表
|
||||
if(SHADER_GROUPS)
|
||||
list(REMOVE_DUPLICATES SHADER_GROUPS)
|
||||
endif()
|
||||
|
||||
# 处理每个着色器组
|
||||
foreach(GROUP_ID ${SHADER_GROUPS})
|
||||
set(GROUP_SOURCES "${${GROUP_ID}_SOURCES}")
|
||||
set(GROUP_NAME "${${GROUP_ID}_NAME}")
|
||||
set(GROUP_DIR "${${GROUP_ID}_DIR}")
|
||||
|
||||
# 每个着色器组的输出目录
|
||||
set(SPIRV_OUTPUT_DIR "${SHADER_INTERMEDIATE_DIR}/${GROUP_NAME}")
|
||||
set(GENERATED_HPP "${SHADER_OUTPUT_DIR}/${GROUP_NAME}_bindings.hpp")
|
||||
|
||||
file(MAKE_DIRECTORY "${SPIRV_OUTPUT_DIR}")
|
||||
|
||||
set(GROUP_SPV_FILES "")
|
||||
|
||||
# 编译组内的每个着色器
|
||||
foreach(SHADER_SOURCE ${GROUP_SOURCES})
|
||||
get_filename_component(SHADER_FILENAME "${SHADER_SOURCE}" NAME)
|
||||
_get_shader_stage_from_extension("${SHADER_SOURCE}" SHADER_STAGE)
|
||||
|
||||
if(NOT SHADER_STAGE)
|
||||
message(WARNING "Skipping unknown shader type: ${SHADER_SOURCE}")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
set(SPV_OUTPUT "${SPIRV_OUTPUT_DIR}/${SHADER_FILENAME}.spv")
|
||||
|
||||
# 构建编译命令参数
|
||||
set(COMPILE_ARGS "")
|
||||
|
||||
if(GLSL_COMPILER_TYPE STREQUAL "glslc")
|
||||
list(APPEND COMPILE_ARGS "-fshader-stage=${SHADER_STAGE}")
|
||||
list(APPEND COMPILE_ARGS "--target-env=vulkan1.3")
|
||||
list(APPEND COMPILE_ARGS "-o" "${SPV_OUTPUT}")
|
||||
|
||||
foreach(INC_DIR ${INCLUDE_ARGS})
|
||||
list(APPEND COMPILE_ARGS "-I${INC_DIR}")
|
||||
endforeach()
|
||||
|
||||
foreach(DEF ${SHADER_DEFINES})
|
||||
list(APPEND COMPILE_ARGS "-D${DEF}")
|
||||
endforeach()
|
||||
|
||||
list(APPEND COMPILE_ARGS "${SHADER_SOURCE}")
|
||||
else()
|
||||
list(APPEND COMPILE_ARGS "-V")
|
||||
list(APPEND COMPILE_ARGS "--target-env" "vulkan1.3")
|
||||
list(APPEND COMPILE_ARGS "-S" "${SHADER_STAGE}")
|
||||
list(APPEND COMPILE_ARGS "-o" "${SPV_OUTPUT}")
|
||||
|
||||
foreach(INC_DIR ${INCLUDE_ARGS})
|
||||
list(APPEND COMPILE_ARGS "-I${INC_DIR}")
|
||||
endforeach()
|
||||
|
||||
foreach(DEF ${SHADER_DEFINES})
|
||||
list(APPEND COMPILE_ARGS "-D${DEF}")
|
||||
endforeach()
|
||||
|
||||
list(APPEND COMPILE_ARGS "${SHADER_SOURCE}")
|
||||
endif()
|
||||
|
||||
# 创建编译命令
|
||||
add_custom_command(
|
||||
OUTPUT "${SPV_OUTPUT}"
|
||||
COMMAND ${GLSL_COMPILER} ${COMPILE_ARGS}
|
||||
DEPENDS "${SHADER_SOURCE}"
|
||||
COMMENT "Compiling GLSL: ${SHADER_FILENAME}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
list(APPEND GROUP_SPV_FILES "${SPV_OUTPUT}")
|
||||
list(APPEND ALL_SPV_FILES "${SPV_OUTPUT}")
|
||||
endforeach()
|
||||
|
||||
# 使用 mirai_shader_compile 工具生成反射和绑定
|
||||
if(GROUP_SPV_FILES)
|
||||
# 创建标记文件表示编译完成
|
||||
set(COMPILED_MARKER "${SPIRV_OUTPUT_DIR}/.compiled")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${COMPILED_MARKER}"
|
||||
COMMAND ${CMAKE_COMMAND} -E touch "${COMPILED_MARKER}"
|
||||
DEPENDS ${GROUP_SPV_FILES}
|
||||
COMMENT "GLSL shaders compiled: ${GROUP_NAME}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# 生成绑定头文件
|
||||
add_custom_command(
|
||||
OUTPUT "${GENERATED_HPP}"
|
||||
COMMAND $<TARGET_FILE:${MIRAI_SHADER_COMPILER_TARGET}>
|
||||
--dir "${SPIRV_OUTPUT_DIR}"
|
||||
--output "${GENERATED_HPP}"
|
||||
--name "${GROUP_NAME}"
|
||||
DEPENDS "${COMPILED_MARKER}" ${MIRAI_SHADER_COMPILER_TARGET}
|
||||
COMMENT "Generating bindings: ${GROUP_NAME}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
list(APPEND ALL_GENERATED_HEADERS "${GENERATED_HPP}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# 附加到目标:添加生成目录到 include 路径
|
||||
target_include_directories(${SHADER_TARGET} PUBLIC
|
||||
$<BUILD_INTERFACE:${SHADER_OUTPUT_DIR}>
|
||||
)
|
||||
|
||||
# 添加编译依赖
|
||||
if(ALL_GENERATED_HEADERS)
|
||||
add_custom_target(${SHADER_TARGET}_shaders DEPENDS ${ALL_GENERATED_HEADERS})
|
||||
add_dependencies(${SHADER_TARGET} ${SHADER_TARGET}_shaders)
|
||||
endif()
|
||||
|
||||
# 导出变量
|
||||
set(${SHADER_TARGET}_SHADER_HEADERS ${ALL_GENERATED_HEADERS} PARENT_SCOPE)
|
||||
set(${SHADER_TARGET}_SHADER_OUTPUT_DIR ${SHADER_OUTPUT_DIR} PARENT_SCOPE)
|
||||
set(${SHADER_TARGET}_SPV_FILES ${ALL_SPV_FILES} PARENT_SCOPE)
|
||||
|
||||
message(STATUS "Added GLSL shaders to target: ${SHADER_TARGET}")
|
||||
message(STATUS " Output: ${SHADER_OUTPUT_DIR}")
|
||||
if(ALL_SHADERS)
|
||||
list(LENGTH ALL_SHADERS SHADER_COUNT)
|
||||
message(STATUS " Shaders: ${SHADER_COUNT} files")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# ============================================================================
|
||||
# add_shader_library - 兼容性别名(调用 add_glsl_shader_library)
|
||||
# ============================================================================
|
||||
#
|
||||
# 为了向后兼容,保留 add_shader_library 函数名
|
||||
# 现在默认使用 GLSL 编译
|
||||
#
|
||||
function(add_shader_library)
|
||||
# 解析参数
|
||||
set(options RECURSIVE)
|
||||
set(oneValueArgs TARGET SHADER_PATH)
|
||||
set(multiValueArgs SHADERS REF_PATHS DEFINES)
|
||||
cmake_parse_arguments(SHADER "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
# 转换参数并调用新函数
|
||||
set(FORWARD_ARGS "TARGET" "${SHADER_TARGET}")
|
||||
|
||||
if(SHADER_SHADER_PATH)
|
||||
list(APPEND FORWARD_ARGS "SHADER_PATH" "${SHADER_SHADER_PATH}")
|
||||
endif()
|
||||
|
||||
if(SHADER_SHADERS)
|
||||
list(APPEND FORWARD_ARGS "SHADERS" ${SHADER_SHADERS})
|
||||
endif()
|
||||
|
||||
if(SHADER_REF_PATHS)
|
||||
list(APPEND FORWARD_ARGS "INCLUDE_DIRS" ${SHADER_REF_PATHS})
|
||||
endif()
|
||||
|
||||
if(SHADER_DEFINES)
|
||||
list(APPEND FORWARD_ARGS "DEFINES" ${SHADER_DEFINES})
|
||||
endif()
|
||||
|
||||
if(SHADER_RECURSIVE)
|
||||
list(APPEND FORWARD_ARGS "RECURSIVE")
|
||||
endif()
|
||||
|
||||
add_glsl_shader_library(${FORWARD_ARGS})
|
||||
endfunction()
|
||||
@@ -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
|
||||
|
||||
## 背景
|
||||
|
||||
|
||||
192
docs/adr/adr-003-glsl-shader-system.md
Normal file
192
docs/adr/adr-003-glsl-shader-system.md
Normal file
@@ -0,0 +1,192 @@
|
||||
---
|
||||
version: 0.2.0
|
||||
status: accepted
|
||||
created: 2024-01-20
|
||||
author: MIRAI Team
|
||||
supersedes: adr-002-slang-shader-system.md
|
||||
---
|
||||
|
||||
# ADR-003: 迁移到 GLSL 着色器系统
|
||||
|
||||
## 状态
|
||||
|
||||
**已接受** - 2024-01-20
|
||||
|
||||
## 背景
|
||||
|
||||
原系统使用 Slang 着色器语言(参见 [ADR-002](adr-002-slang-shader-system.md)),虽然 Slang 提供了强大的反射和泛型功能,但在实际使用中遇到了以下问题:
|
||||
|
||||
1. **工具链复杂性**:Slang 编译器需要额外的依赖和配置
|
||||
2. **用户学习成本**:开发者需要学习新的着色器语言
|
||||
3. **调试困难**:Slang 生成的 SPIR-V 难以调试
|
||||
4. **生态系统兼容性**:大多数着色器工具和示例都基于 GLSL
|
||||
|
||||
## 决策
|
||||
|
||||
迁移到标准 **GLSL** 着色器语言,采用以下架构设计:
|
||||
|
||||
### 1. 资源分配约定
|
||||
|
||||
| 资源类型 | 分配 | 说明 |
|
||||
|---------|------|------|
|
||||
| **Set 0** | 框架专用 | 用户不要使用 |
|
||||
| **Set 1** | 用户 UBO | 用户自定义 Uniform Buffer |
|
||||
| **Set 2** | 用户纹理 | 用户自定义纹理 |
|
||||
| **Push Constant** | 用户专用 | 最大 128 bytes |
|
||||
|
||||
### 2. 框架 UBO 结构 (Set 0, Binding 0)
|
||||
|
||||
```glsl
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds; // (x, y, width, height)
|
||||
float u_opacity; // 0.0 - 1.0
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
```
|
||||
|
||||
### 3. 四种渲染模式
|
||||
|
||||
| 模式 | 顶点着色器 | 片段着色器 | 说明 |
|
||||
|------|-----------|-----------|------|
|
||||
| **Backdrop** | 框架提供 | 用户提供 | 后处理效果,框架提供背景纹理 |
|
||||
| **Procedural** | 框架提供 | 用户提供 | 程序化生成 |
|
||||
| **Mask** | 框架提供 | 用户提供 | 遮罩效果 |
|
||||
| **Custom Mesh** | 用户提供 | 用户提供 | 自定义网格渲染 |
|
||||
|
||||
### 4. Push Constant 完全留给用户
|
||||
|
||||
框架不使用 Push Constant,用户可以完全控制这 128 bytes 的快速更新数据。
|
||||
|
||||
## 理由
|
||||
|
||||
### 1. 简化工具链
|
||||
|
||||
```
|
||||
原流程: .slang → slangc → SPIR-V + 反射 JSON → C++ 绑定
|
||||
新流程: .glsl → glslangValidator → SPIR-V
|
||||
```
|
||||
|
||||
### 2. 用户友好
|
||||
|
||||
- GLSL 是业界标准,大多数图形开发者已经熟悉
|
||||
- 丰富的学习资源和示例代码
|
||||
- 更好的 IDE 支持和语法高亮
|
||||
|
||||
### 3. 调试便利
|
||||
|
||||
- SPIR-V 工具链成熟
|
||||
- RenderDoc 等工具原生支持
|
||||
- 错误信息更易理解
|
||||
|
||||
### 4. 灵活的用户控制
|
||||
|
||||
- Push Constant 完全留给用户,支持高频参数更新
|
||||
- 用户可以自由定义 Set 1/2 的资源布局
|
||||
- 模板文件提供清晰的起点
|
||||
|
||||
## 替代方案
|
||||
|
||||
| 方案 | 优点 | 缺点 |
|
||||
|------|------|------|
|
||||
| **GLSL** (选择) | 标准、简单、广泛支持 | 无反射、手动绑定 |
|
||||
| Slang | 反射、泛型、多后端 | 工具链复杂、学习成本 |
|
||||
| HLSL | DX12 原生 | 仅限 Windows |
|
||||
| WGSL | WebGPU 原生 | 生态不成熟 |
|
||||
|
||||
## 后果
|
||||
|
||||
### 积极
|
||||
|
||||
- 简化了编译工具链
|
||||
- 降低了用户学习成本
|
||||
- 用户可以完全控制 Push Constant
|
||||
- 更好的调试体验
|
||||
- 更广泛的社区支持
|
||||
|
||||
### 消极
|
||||
|
||||
- 失去了 Slang 的自动反射功能
|
||||
- 需要用户手动复制着色器模板
|
||||
- 需要手动维护 C++ 和 GLSL 的数据结构同步
|
||||
|
||||
## 实现
|
||||
|
||||
### 着色器编译流程
|
||||
|
||||
```
|
||||
用户 GLSL 片段着色器
|
||||
↓
|
||||
glslangValidator -V
|
||||
↓
|
||||
SPIR-V
|
||||
↓
|
||||
spirv-cross (可选反射)
|
||||
```
|
||||
|
||||
### 目录结构
|
||||
|
||||
```
|
||||
src/shader/shaders/glsl/
|
||||
├── framework/ # 框架内置着色器
|
||||
│ ├── quad.vert # 四边形顶点着色器
|
||||
│ └── fullscreen.vert # 全屏顶点着色器
|
||||
├── templates/ # 用户模板
|
||||
│ ├── procedural.frag.template
|
||||
│ ├── backdrop.frag.template
|
||||
│ ├── mask.frag.template
|
||||
│ └── mesh.vert.template
|
||||
└── examples/ # 示例着色器
|
||||
├── gradient.frag
|
||||
├── blur.frag
|
||||
└── rounded_rect.frag
|
||||
```
|
||||
|
||||
### CMake 集成
|
||||
|
||||
```cmake
|
||||
function(compile_glsl_shader INPUT OUTPUT)
|
||||
add_custom_command(
|
||||
OUTPUT ${OUTPUT}
|
||||
COMMAND glslangValidator -V ${INPUT} -o ${OUTPUT}
|
||||
DEPENDS ${INPUT}
|
||||
COMMENT "Compiling ${INPUT}"
|
||||
)
|
||||
endfunction()
|
||||
```
|
||||
|
||||
### C++ 端 UBO 结构
|
||||
|
||||
```cpp
|
||||
namespace mirai {
|
||||
|
||||
struct alignas(16) widget_bounds_ubo {
|
||||
float bounds[4]; // x, y, width, height
|
||||
float opacity;
|
||||
float _pad[3];
|
||||
};
|
||||
static_assert(sizeof(widget_bounds_ubo) == 32);
|
||||
|
||||
} // namespace mirai
|
||||
```
|
||||
|
||||
## 迁移指南
|
||||
|
||||
### 从 Slang 迁移到 GLSL
|
||||
|
||||
1. **复制模板文件**:根据渲染模式选择对应的模板
|
||||
2. **转换语法**:
|
||||
- `float4` → `vec4`
|
||||
- `float2` → `vec2`
|
||||
- `ConstantBuffer<T>` → `uniform T { ... }`
|
||||
- `[[vk::binding(n, m)]]` → `layout(set = m, binding = n)`
|
||||
3. **更新绑定**:确保使用正确的 Set 和 Binding
|
||||
4. **测试验证**:使用 glslangValidator 验证语法
|
||||
|
||||
## 参考
|
||||
|
||||
- [GLSL 规范](https://www.khronos.org/opengl/wiki/Core_Language_(GLSL))
|
||||
- [Vulkan GLSL](https://www.khronos.org/opengl/wiki/Vulkan_GLSL)
|
||||
- [glslangValidator](https://github.com/KhronosGroup/glslang)
|
||||
- [MIRAI GLSL 着色器系统设计](../../plans/glsl_shader_system_design.md)
|
||||
1002
docs/demo_architecture.md
Normal file
1002
docs/demo_architecture.md
Normal file
File diff suppressed because it is too large
Load Diff
646
docs/shader_widget_architecture.md
Normal file
646
docs/shader_widget_architecture.md
Normal file
@@ -0,0 +1,646 @@
|
||||
# MIRAI Shader Widget 统一接口架构设计
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本文档描述 MIRAI 框架的着色器控件(Shader Widget)统一接口架构设计。该架构基于标准 GLSL 着色器语言,支持四种渲染模式,每种模式提供特定的核心功能,而具体效果参数由派生类和着色器定义。
|
||||
|
||||
> **注意**:本文档反映了从 Slang 迁移到 GLSL 后的新架构。详见 [ADR-003: 迁移到 GLSL 着色器系统](adr/adr-003-glsl-shader-system.md)。
|
||||
|
||||
### 1.1 设计目标
|
||||
|
||||
1. **使用标准 GLSL**:采用业界标准着色器语言,降低学习成本
|
||||
2. **Push Constant 完全留给用户**:框架不使用 Push Constant,用户可完全控制
|
||||
3. **框架数据最小化**:只提供 bounds 和 opacity
|
||||
4. **简单约定**:提前约定框架和用户各自使用的资源
|
||||
5. **模板驱动**:提供模板让用户复制和修改
|
||||
|
||||
### 1.2 四种渲染模式
|
||||
|
||||
| 模式 | 顶点着色器 | 片段着色器 | 说明 |
|
||||
|------|-----------|-----------|------|
|
||||
| **Backdrop** | 框架提供 | 用户提供 | 后处理效果,框架提供背景纹理 |
|
||||
| **Procedural** | 框架提供 | 用户提供 | 程序化生成 |
|
||||
| **Mask** | 框架提供 | 用户提供 | 遮罩效果 |
|
||||
| **Custom Mesh** | 用户提供 | 用户提供 | 自定义网格渲染 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 资源分配约定
|
||||
|
||||
### 2.1 Descriptor Set 布局
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 资源分配约定表 │
|
||||
├─────────────────┬───────────────────────────────────────────┤
|
||||
│ 资源类型 │ 分配 │
|
||||
├─────────────────┼───────────────────────────────────────────┤
|
||||
│ Set 0 │ 框架专用(用户不要使用) │
|
||||
│ Set 1 │ 用户 UBO │
|
||||
│ Set 2 │ 用户纹理 │
|
||||
│ Push Constant │ 用户专用(最大 128 bytes) │
|
||||
├─────────────────┼───────────────────────────────────────────┤
|
||||
│ 顶点着色器 │ 框架提供(Custom Mesh 除外) │
|
||||
│ 片段着色器 │ 用户提供 │
|
||||
└─────────────────┴───────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 Set 0:框架专用
|
||||
|
||||
#### 通用模式(Procedural, Mask)
|
||||
|
||||
```glsl
|
||||
// Set 0: 框架专用 - 用户不要在 Set 0 定义任何资源
|
||||
|
||||
// Binding 0: Widget 边界
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds; // (x, y, width, height)
|
||||
float u_opacity; // 0.0 - 1.0
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
// 大小: 32 bytes
|
||||
```
|
||||
|
||||
#### Backdrop 模式
|
||||
|
||||
```glsl
|
||||
// Binding 0: Widget 边界(同上)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// Binding 1: 背景纹理(框架自动绑定)
|
||||
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
|
||||
```
|
||||
|
||||
### 2.3 Set 1:用户 UBO
|
||||
|
||||
用户可以在 Set 1 定义自己的 Uniform Buffer:
|
||||
|
||||
```glsl
|
||||
// 示例:模糊参数
|
||||
layout(set = 1, binding = 0, std140) uniform BlurParams {
|
||||
float sigma; // 高斯分布标准差
|
||||
int samples; // 采样半径
|
||||
vec2 texel_size; // 纹理像素大小
|
||||
} params;
|
||||
```
|
||||
|
||||
### 2.4 Set 2:用户纹理
|
||||
|
||||
用户可以在 Set 2 定义自己的纹理:
|
||||
|
||||
```glsl
|
||||
// 示例:自定义纹理
|
||||
layout(set = 2, binding = 0) uniform sampler2D u_my_texture;
|
||||
layout(set = 2, binding = 1) uniform sampler2D u_noise_texture;
|
||||
```
|
||||
|
||||
### 2.5 Push Constant:用户专用
|
||||
|
||||
Push Constant 完全留给用户,最大 128 bytes:
|
||||
|
||||
```glsl
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color_start; // 起始颜色
|
||||
vec4 color_end; // 结束颜色
|
||||
float angle; // 渐变角度
|
||||
float _pad[3];
|
||||
} pc;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 框架内置顶点着色器
|
||||
|
||||
框架为 Backdrop/Procedural/Mask 模式提供内置顶点着色器,用户无需编写。
|
||||
|
||||
### 3.1 顶点着色器输出
|
||||
|
||||
框架的顶点着色器会输出以下数据给片段着色器:
|
||||
|
||||
```glsl
|
||||
// 框架顶点着色器输出(用户片段着色器的输入)
|
||||
layout(location = 0) in vec2 v_uv; // UV 坐标 (0-1)
|
||||
layout(location = 1) in vec2 v_local_pos; // 局部坐标 (0-1),相对于 widget bounds
|
||||
```
|
||||
|
||||
### 3.2 框架顶点着色器实现
|
||||
|
||||
```glsl
|
||||
// 框架内部实现 - quad.vert
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 a_position;
|
||||
layout(location = 1) in vec2 a_uv;
|
||||
|
||||
layout(location = 0) out vec2 v_uv;
|
||||
layout(location = 1) out vec2 v_local_pos;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
void main() {
|
||||
// 将顶点位置转换到 NDC
|
||||
vec2 pos = a_position * widget.u_bounds.zw + widget.u_bounds.xy;
|
||||
vec2 ndc = pos * 2.0 - 1.0;
|
||||
gl_Position = vec4(ndc, 0.0, 1.0);
|
||||
|
||||
v_uv = a_uv;
|
||||
v_local_pos = a_position;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 用户片段着色器模板
|
||||
|
||||
### 4.1 Procedural 模式模板
|
||||
|
||||
用于程序化生成效果(渐变、噪声、图案等):
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 框架输入(不要修改)
|
||||
// ============================================================
|
||||
layout(location = 0) in vec2 v_uv; // UV 坐标 (0-1)
|
||||
layout(location = 1) in vec2 v_local_pos; // 局部坐标 (0-1)
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用(不要修改)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds; // (x, y, width, height)
|
||||
float u_opacity; // 0.0 - 1.0
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// ============================================================
|
||||
// 用户区域(自由定义)
|
||||
// ============================================================
|
||||
|
||||
// Set 1: 用户 UBO(可选)
|
||||
// layout(set = 1, binding = 0, std140) uniform MyParams {
|
||||
// float time;
|
||||
// // ... 自定义参数
|
||||
// } params;
|
||||
|
||||
// Set 2: 用户纹理(可选)
|
||||
// layout(set = 2, binding = 0) uniform sampler2D u_my_texture;
|
||||
|
||||
// Push Constant(可选,最大 128 bytes)
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color;
|
||||
// ... 自定义参数
|
||||
} pc;
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
void main() {
|
||||
// TODO: 用户实现程序化效果
|
||||
frag_color = pc.color;
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Backdrop 模式模板
|
||||
|
||||
用于后处理效果(模糊、颜色调整等):
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 框架输入(不要修改)
|
||||
// ============================================================
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用(不要修改)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// 背景纹理(框架提供)
|
||||
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
|
||||
|
||||
// ============================================================
|
||||
// 用户区域(自由定义)
|
||||
// ============================================================
|
||||
|
||||
// Set 1: 用户 UBO(可选)
|
||||
// layout(set = 1, binding = 0, std140) uniform MyParams {
|
||||
// float radius;
|
||||
// // ... 自定义参数
|
||||
// } params;
|
||||
|
||||
// Push Constant(可选)
|
||||
// layout(push_constant) uniform PushConstants {
|
||||
// // ... 自定义参数
|
||||
// } pc;
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
void main() {
|
||||
// 采样背景纹理
|
||||
vec4 color = texture(u_backdrop, v_uv);
|
||||
|
||||
// TODO: 用户实现后处理效果
|
||||
|
||||
frag_color = color;
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 Mask 模式模板
|
||||
|
||||
用于遮罩效果(圆角矩形、圆形、自定义形状等):
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 框架输入(不要修改)
|
||||
// ============================================================
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用(不要修改)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// ============================================================
|
||||
// 用户区域(自由定义)
|
||||
// ============================================================
|
||||
|
||||
// Push Constant(可选)
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color; // 遮罩颜色
|
||||
float corner_radius; // 圆角半径
|
||||
float _pad[3];
|
||||
} pc;
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
void main() {
|
||||
// 示例:圆角矩形遮罩
|
||||
vec2 size = widget.u_bounds.zw;
|
||||
vec2 half_size = size * 0.5;
|
||||
vec2 p = (v_local_pos - 0.5) * size;
|
||||
|
||||
vec2 q = abs(p) - half_size + pc.corner_radius;
|
||||
float d = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - pc.corner_radius;
|
||||
float mask = 1.0 - smoothstep(-1.0, 1.0, d);
|
||||
|
||||
frag_color = pc.color;
|
||||
frag_color.a *= mask * widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 Custom Mesh 模式模板
|
||||
|
||||
用于自定义网格渲染(粒子系统、自定义形状等):
|
||||
|
||||
**顶点着色器模板:**
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 用户定义顶点输入
|
||||
// ============================================================
|
||||
layout(location = 0) in vec3 a_position;
|
||||
layout(location = 1) in vec2 a_uv;
|
||||
layout(location = 2) in vec4 a_color;
|
||||
|
||||
layout(location = 0) out vec2 v_uv;
|
||||
layout(location = 1) out vec4 v_color;
|
||||
|
||||
// Set 0: 框架专用(不要修改)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// ============================================================
|
||||
// 用户区域(自由定义)
|
||||
// ============================================================
|
||||
|
||||
// Push Constant
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 mvp;
|
||||
} pc;
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
void main() {
|
||||
gl_Position = pc.mvp * vec4(a_position, 1.0);
|
||||
v_uv = a_uv;
|
||||
v_color = a_color;
|
||||
}
|
||||
```
|
||||
|
||||
**片段着色器模板:**
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec4 v_color;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用(不要修改)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// ============================================================
|
||||
// 用户区域(自由定义)
|
||||
// ============================================================
|
||||
|
||||
// Set 2: 用户纹理(可选)
|
||||
layout(set = 2, binding = 0) uniform sampler2D u_texture;
|
||||
|
||||
void main() {
|
||||
frag_color = texture(u_texture, v_uv) * v_color;
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 编译系统
|
||||
|
||||
### 5.1 编译流程
|
||||
|
||||
```
|
||||
用户 GLSL 着色器
|
||||
↓
|
||||
glslangValidator -V
|
||||
↓
|
||||
SPIR-V
|
||||
↓
|
||||
spirv-cross (可选反射)
|
||||
```
|
||||
|
||||
### 5.2 CMake 集成
|
||||
|
||||
```cmake
|
||||
# cmake/shader_compile.cmake
|
||||
|
||||
function(compile_glsl_shader INPUT OUTPUT)
|
||||
get_filename_component(INPUT_NAME ${INPUT} NAME)
|
||||
add_custom_command(
|
||||
OUTPUT ${OUTPUT}
|
||||
COMMAND glslangValidator -V ${INPUT} -o ${OUTPUT}
|
||||
DEPENDS ${INPUT}
|
||||
COMMENT "Compiling GLSL shader: ${INPUT_NAME}"
|
||||
VERBATIM
|
||||
)
|
||||
endfunction()
|
||||
|
||||
# 批量编译着色器
|
||||
function(compile_glsl_shaders TARGET_NAME SHADER_DIR OUTPUT_DIR)
|
||||
file(GLOB_RECURSE SHADER_FILES
|
||||
"${SHADER_DIR}/*.vert"
|
||||
"${SHADER_DIR}/*.frag"
|
||||
"${SHADER_DIR}/*.comp"
|
||||
)
|
||||
|
||||
set(SPIRV_FILES)
|
||||
foreach(SHADER ${SHADER_FILES})
|
||||
get_filename_component(SHADER_NAME ${SHADER} NAME)
|
||||
set(SPIRV_FILE "${OUTPUT_DIR}/${SHADER_NAME}.spv")
|
||||
compile_glsl_shader(${SHADER} ${SPIRV_FILE})
|
||||
list(APPEND SPIRV_FILES ${SPIRV_FILE})
|
||||
endforeach()
|
||||
|
||||
add_custom_target(${TARGET_NAME} DEPENDS ${SPIRV_FILES})
|
||||
endfunction()
|
||||
```
|
||||
|
||||
### 5.3 目录结构
|
||||
|
||||
```
|
||||
src/shader/shaders/glsl/
|
||||
├── framework/ # 框架内置着色器
|
||||
│ ├── quad.vert # 四边形顶点着色器
|
||||
│ └── fullscreen.vert # 全屏顶点着色器
|
||||
├── templates/ # 用户模板(复制使用)
|
||||
│ ├── procedural.frag.template
|
||||
│ ├── backdrop.frag.template
|
||||
│ ├── mask.frag.template
|
||||
│ └── mesh.vert.template
|
||||
└── examples/ # 示例着色器
|
||||
├── gradient.frag # 渐变效果
|
||||
├── blur.frag # 模糊效果
|
||||
└── rounded_rect.frag # 圆角矩形
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. C++ 端实现
|
||||
|
||||
### 6.1 框架 UBO 结构
|
||||
|
||||
```cpp
|
||||
namespace mirai {
|
||||
|
||||
struct alignas(16) widget_bounds_ubo {
|
||||
float bounds[4]; // x, y, width, height
|
||||
float opacity;
|
||||
float _pad[3];
|
||||
};
|
||||
static_assert(sizeof(widget_bounds_ubo) == 32);
|
||||
|
||||
} // namespace mirai
|
||||
```
|
||||
|
||||
### 6.2 描述符集布局创建
|
||||
|
||||
```cpp
|
||||
namespace mirai {
|
||||
|
||||
// 通用模式 Set 0 布局
|
||||
inline vk::UniqueDescriptorSetLayout create_set0_layout_common(vk::Device device) {
|
||||
vk::DescriptorSetLayoutBinding binding{
|
||||
0, vk::DescriptorType::eUniformBuffer, 1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment
|
||||
};
|
||||
return device.createDescriptorSetLayoutUnique({{}, 1, &binding});
|
||||
}
|
||||
|
||||
// Backdrop 模式 Set 0 布局
|
||||
inline vk::UniqueDescriptorSetLayout create_set0_layout_backdrop(vk::Device device) {
|
||||
std::array<vk::DescriptorSetLayoutBinding, 2> bindings = {{
|
||||
{0, vk::DescriptorType::eUniformBuffer, 1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1,
|
||||
vk::ShaderStageFlagBits::eFragment}
|
||||
}};
|
||||
return device.createDescriptorSetLayoutUnique({{}, bindings});
|
||||
}
|
||||
|
||||
} // namespace mirai
|
||||
```
|
||||
|
||||
### 6.3 Push Constant 范围
|
||||
|
||||
```cpp
|
||||
// 用户 Push Constant 范围(128 bytes)
|
||||
vk::PushConstantRange user_push_constant_range{
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
|
||||
0, // offset
|
||||
128 // size (最大)
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 整体架构图
|
||||
|
||||
### 7.1 系统架构总览
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph BuildTime[构建时]
|
||||
A[GLSL 源文件] --> B[glslangValidator]
|
||||
B --> C[SPIR-V 字节码]
|
||||
end
|
||||
|
||||
subgraph Runtime[运行时]
|
||||
C --> D[shader_library]
|
||||
D --> E[shader_module]
|
||||
|
||||
subgraph ShaderWidgets[Shader Widget 层]
|
||||
F[shader_widget 基类]
|
||||
G[backdrop_widget]
|
||||
H[procedural_widget]
|
||||
I[mask_widget]
|
||||
J[custom_mesh_widget]
|
||||
|
||||
F --> G
|
||||
F --> H
|
||||
F --> I
|
||||
F --> J
|
||||
end
|
||||
|
||||
E --> F
|
||||
|
||||
subgraph Resources[资源管理]
|
||||
K[gpu_allocator]
|
||||
L[descriptor_pool]
|
||||
end
|
||||
|
||||
F --> K
|
||||
F --> L
|
||||
|
||||
subgraph Rendering[渲染管线]
|
||||
M[pipeline]
|
||||
N[command_buffer]
|
||||
end
|
||||
|
||||
F --> M
|
||||
M --> N
|
||||
end
|
||||
```
|
||||
|
||||
### 7.2 数据流
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as 用户代码
|
||||
participant Widget as shader_widget
|
||||
participant UBO as Uniform Buffer
|
||||
participant PC as Push Constant
|
||||
participant GPU as GPU
|
||||
|
||||
User->>Widget: 设置参数
|
||||
Widget->>UBO: 更新 Set 1 UBO
|
||||
Widget->>PC: 更新 Push Constant
|
||||
|
||||
loop 每帧渲染
|
||||
Widget->>GPU: vkCmdBindPipeline
|
||||
Widget->>GPU: vkCmdBindDescriptorSets (Set 0, 1, 2)
|
||||
Widget->>GPU: vkCmdPushConstants
|
||||
Widget->>GPU: vkCmdDraw
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 资源分配总结
|
||||
|
||||
### 8.1 资源归属表
|
||||
|
||||
| 资源 | 归属 | 说明 |
|
||||
|------|------|------|
|
||||
| Set 0 | 框架 | WidgetBounds + Backdrop纹理 |
|
||||
| Set 1 | 用户 | 用户 UBO |
|
||||
| Set 2 | 用户 | 用户纹理 |
|
||||
| Push Constant | 用户 | 最大 128 bytes |
|
||||
| 顶点着色器 | 框架 | Custom Mesh 除外 |
|
||||
| 片段着色器 | 用户 | 所有模式 |
|
||||
|
||||
### 8.2 框架提供的数据
|
||||
|
||||
| 数据 | 位置 | 类型 | 说明 |
|
||||
|------|------|------|------|
|
||||
| u_bounds | Set 0, Binding 0 | vec4 | Widget 边界 (x, y, w, h) |
|
||||
| u_opacity | Set 0, Binding 0 | float | 透明度 (0-1) |
|
||||
| u_backdrop | Set 0, Binding 1 | sampler2D | 背景纹理(仅 Backdrop 模式) |
|
||||
|
||||
### 8.3 框架顶点着色器输出
|
||||
|
||||
| 输出 | location | 类型 | 说明 |
|
||||
|------|----------|------|------|
|
||||
| v_uv | 0 | vec2 | UV 坐标 (0-1) |
|
||||
| v_local_pos | 1 | vec2 | 局部坐标 (0-1) |
|
||||
|
||||
---
|
||||
|
||||
## 9. 相关文档
|
||||
|
||||
- [ADR-003: 迁移到 GLSL 着色器系统](adr/adr-003-glsl-shader-system.md) - 架构决策记录
|
||||
- [GLSL 着色器系统设计](../plans/glsl_shader_system_design.md) - 详细设计文档
|
||||
- [Shader Widget 使用示例](shader_widget_examples.md) - 使用示例
|
||||
842
docs/shader_widget_examples.md
Normal file
842
docs/shader_widget_examples.md
Normal file
@@ -0,0 +1,842 @@
|
||||
# MIRAI Shader Widget 使用示例
|
||||
|
||||
本文档提供 MIRAI 框架着色器控件系统的详细使用示例,涵盖四种渲染模式和自定义控件开发。
|
||||
|
||||
> **注意**:本文档使用标准 GLSL 着色器语言。详见 [ADR-003: 迁移到 GLSL 着色器系统](adr/adr-003-glsl-shader-system.md)。
|
||||
|
||||
## 目录
|
||||
|
||||
1. [Procedural 模式示例](#1-procedural-模式示例)
|
||||
2. [Backdrop 模式示例](#2-backdrop-模式示例)
|
||||
3. [Mask 模式示例](#3-mask-模式示例)
|
||||
4. [Custom Mesh 模式示例](#4-custom-mesh-模式示例)
|
||||
5. [Push Constant 使用示例](#5-push-constant-使用示例)
|
||||
6. [用户 UBO 使用示例](#6-用户-ubo-使用示例)
|
||||
|
||||
---
|
||||
|
||||
## 1. Procedural 模式示例
|
||||
|
||||
Procedural 模式用于程序化渲染,基于坐标生成图形。框架提供顶点着色器,用户只需编写片段着色器。
|
||||
|
||||
### 1.1 线性渐变效果
|
||||
|
||||
使用 Push Constant 传递颜色和角度参数:
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 线性渐变效果 (Procedural 模式)
|
||||
// ============================================================
|
||||
|
||||
// 框架输入
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
// Push Constant: 渐变参数
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color_start; // 起始颜色
|
||||
vec4 color_end; // 结束颜色
|
||||
float angle; // 渐变角度(弧度)
|
||||
float _pad[3];
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
// 计算渐变方向向量
|
||||
vec2 dir = vec2(cos(pc.angle), sin(pc.angle));
|
||||
|
||||
// 计算当前位置在渐变方向上的投影
|
||||
float t = dot(v_local_pos - 0.5, dir) + 0.5;
|
||||
|
||||
// 线性插值颜色
|
||||
frag_color = mix(pc.color_start, pc.color_end, clamp(t, 0.0, 1.0));
|
||||
|
||||
// 应用框架透明度
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 径向渐变效果
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
// 框架输入
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
// Push Constant: 径向渐变参数
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color_center; // 中心颜色
|
||||
vec4 color_edge; // 边缘颜色
|
||||
vec2 center; // 渐变中心 (0-1)
|
||||
float radius; // 渐变半径
|
||||
float _pad;
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
// 计算到中心的距离
|
||||
float dist = length(v_local_pos - pc.center) / pc.radius;
|
||||
|
||||
// 径向插值
|
||||
frag_color = mix(pc.color_center, pc.color_edge, clamp(dist, 0.0, 1.0));
|
||||
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 棋盘格图案
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color_a; // 颜色 A
|
||||
vec4 color_b; // 颜色 B
|
||||
float grid_size; // 格子大小
|
||||
float _pad[3];
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
// 计算格子坐标
|
||||
vec2 grid = floor(v_local_pos * widget.u_bounds.zw / pc.grid_size);
|
||||
|
||||
// 棋盘格模式
|
||||
float checker = mod(grid.x + grid.y, 2.0);
|
||||
|
||||
frag_color = mix(pc.color_a, pc.color_b, checker);
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Backdrop 模式示例
|
||||
|
||||
Backdrop 模式用于后处理效果,框架提供背景纹理 `u_backdrop`。
|
||||
|
||||
### 2.1 高斯模糊效果
|
||||
|
||||
使用用户 UBO (Set 1) 传递模糊参数:
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 高斯模糊效果 (Backdrop 模式)
|
||||
// ============================================================
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
// 背景纹理(框架提供)
|
||||
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
|
||||
|
||||
// Set 1: 用户 UBO - 模糊参数
|
||||
layout(set = 1, binding = 0, std140) uniform BlurParams {
|
||||
float sigma; // 高斯分布标准差
|
||||
int samples; // 采样半径
|
||||
vec2 texel_size; // 纹理像素大小 (1.0/width, 1.0/height)
|
||||
} params;
|
||||
|
||||
void main() {
|
||||
vec4 color = vec4(0.0);
|
||||
float total_weight = 0.0;
|
||||
|
||||
// 高斯模糊核
|
||||
float sigma2 = params.sigma * params.sigma;
|
||||
float inv_2sigma2 = 1.0 / (2.0 * sigma2);
|
||||
|
||||
// 双重循环采样
|
||||
for (int i = -params.samples; i <= params.samples; i++) {
|
||||
for (int j = -params.samples; j <= params.samples; j++) {
|
||||
vec2 offset = vec2(float(i), float(j)) * params.texel_size;
|
||||
|
||||
// 计算高斯权重
|
||||
float dist2 = float(i * i + j * j);
|
||||
float weight = exp(-dist2 * inv_2sigma2);
|
||||
|
||||
color += texture(u_backdrop, v_uv + offset) * weight;
|
||||
total_weight += weight;
|
||||
}
|
||||
}
|
||||
|
||||
frag_color = color / total_weight;
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 色彩调整效果
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
|
||||
|
||||
// Push Constant: 色彩调整参数
|
||||
layout(push_constant) uniform PushConstants {
|
||||
float brightness; // 亮度 (-1 to 1)
|
||||
float contrast; // 对比度 (0 to 2)
|
||||
float saturation; // 饱和度 (0 to 2)
|
||||
float _pad;
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
vec4 color = texture(u_backdrop, v_uv);
|
||||
|
||||
// 亮度调整
|
||||
color.rgb += pc.brightness;
|
||||
|
||||
// 对比度调整
|
||||
color.rgb = (color.rgb - 0.5) * pc.contrast + 0.5;
|
||||
|
||||
// 饱和度调整
|
||||
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
||||
color.rgb = mix(vec3(gray), color.rgb, pc.saturation);
|
||||
|
||||
frag_color = color;
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 毛玻璃效果
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
|
||||
|
||||
// Set 1: 模糊参数
|
||||
layout(set = 1, binding = 0, std140) uniform BlurParams {
|
||||
float sigma;
|
||||
int samples;
|
||||
vec2 texel_size;
|
||||
} blur;
|
||||
|
||||
// Push Constant: 毛玻璃参数
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 tint_color; // 着色颜色
|
||||
float tint_amount; // 着色强度 (0-1)
|
||||
float _pad[3];
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
// 模糊采样
|
||||
vec4 color = vec4(0.0);
|
||||
float total = 0.0;
|
||||
float inv_2sigma2 = 1.0 / (2.0 * blur.sigma * blur.sigma);
|
||||
|
||||
for (int i = -blur.samples; i <= blur.samples; i++) {
|
||||
for (int j = -blur.samples; j <= blur.samples; j++) {
|
||||
vec2 offset = vec2(float(i), float(j)) * blur.texel_size;
|
||||
float w = exp(-float(i*i + j*j) * inv_2sigma2);
|
||||
color += texture(u_backdrop, v_uv + offset) * w;
|
||||
total += w;
|
||||
}
|
||||
}
|
||||
color /= total;
|
||||
|
||||
// 应用着色
|
||||
color.rgb = mix(color.rgb, pc.tint_color.rgb, pc.tint_amount);
|
||||
|
||||
frag_color = color;
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Mask 模式示例
|
||||
|
||||
Mask 模式用于创建遮罩效果,通常输出带有 alpha 通道的颜色。
|
||||
|
||||
### 3.1 圆角矩形遮罩
|
||||
|
||||
使用 SDF(有符号距离场)生成高质量圆角矩形:
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 圆角矩形遮罩 (Mask 模式)
|
||||
// ============================================================
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
// Push Constant: 遮罩参数
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color; // 遮罩颜色
|
||||
float radius; // 圆角半径(像素)
|
||||
float _pad[3];
|
||||
} pc;
|
||||
|
||||
// 圆角矩形 SDF
|
||||
float rounded_rect_sdf(vec2 p, vec2 half_size, float radius) {
|
||||
vec2 q = abs(p) - half_size + radius;
|
||||
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 size = widget.u_bounds.zw;
|
||||
vec2 half_size = size * 0.5;
|
||||
|
||||
// 转换到以中心为原点的坐标系
|
||||
vec2 p = (v_local_pos - 0.5) * size;
|
||||
|
||||
// 计算 SDF
|
||||
float d = rounded_rect_sdf(p, half_size, pc.radius);
|
||||
|
||||
// 抗锯齿
|
||||
float alpha = 1.0 - smoothstep(-1.0, 1.0, d);
|
||||
|
||||
frag_color = pc.color;
|
||||
frag_color.a *= alpha * widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 圆形遮罩
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color;
|
||||
vec2 center; // 圆心 (0-1)
|
||||
float radius; // 半径 (相对于短边)
|
||||
float softness; // 边缘柔和度
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
// 计算宽高比
|
||||
vec2 size = widget.u_bounds.zw;
|
||||
float aspect = size.x / size.y;
|
||||
|
||||
// 调整坐标以保持圆形
|
||||
vec2 uv = v_local_pos;
|
||||
uv.x *= aspect;
|
||||
vec2 c = pc.center;
|
||||
c.x *= aspect;
|
||||
|
||||
// 计算到圆心的距离
|
||||
float dist = length(uv - c);
|
||||
|
||||
// 计算遮罩
|
||||
float min_size = min(size.x, size.y);
|
||||
float r = pc.radius * min_size / size.x; // 调整半径
|
||||
float alpha = 1.0 - smoothstep(r - pc.softness, r + pc.softness, dist);
|
||||
|
||||
frag_color = pc.color;
|
||||
frag_color.a *= alpha * widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 四角独立圆角
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color;
|
||||
vec4 radii; // (左上, 右上, 右下, 左下)
|
||||
} pc;
|
||||
|
||||
// 四角独立圆角 SDF
|
||||
float rounded_rect_sdf_4(vec2 p, vec2 half_size, vec4 radii) {
|
||||
// 根据象限选择圆角半径
|
||||
float r = (p.x > 0.0)
|
||||
? ((p.y > 0.0) ? radii.y : radii.z) // 右上 / 右下
|
||||
: ((p.y > 0.0) ? radii.x : radii.w); // 左上 / 左下
|
||||
|
||||
vec2 q = abs(p) - half_size + r;
|
||||
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 size = widget.u_bounds.zw;
|
||||
vec2 half_size = size * 0.5;
|
||||
vec2 p = (v_local_pos - 0.5) * size;
|
||||
|
||||
float d = rounded_rect_sdf_4(p, half_size, pc.radii);
|
||||
float alpha = 1.0 - smoothstep(-1.0, 1.0, d);
|
||||
|
||||
frag_color = pc.color;
|
||||
frag_color.a *= alpha * widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Custom Mesh 模式示例
|
||||
|
||||
Custom Mesh 模式需要用户同时提供顶点和片段着色器。
|
||||
|
||||
### 4.1 粒子系统
|
||||
|
||||
**顶点着色器:**
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
// 粒子实例数据
|
||||
layout(location = 0) in vec3 a_position; // 粒子位置
|
||||
layout(location = 1) in float a_size; // 粒子大小
|
||||
layout(location = 2) in vec4 a_color; // 粒子颜色
|
||||
layout(location = 3) in float a_rotation; // 粒子旋转
|
||||
|
||||
layout(location = 0) out vec2 v_uv;
|
||||
layout(location = 1) out vec4 v_color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
// Push Constant: 变换矩阵
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 view_proj;
|
||||
} pc;
|
||||
|
||||
// 四边形顶点(Billboard)
|
||||
const vec2 QUAD_VERTICES[6] = vec2[](
|
||||
vec2(-0.5, -0.5), vec2(0.5, -0.5), vec2(0.5, 0.5),
|
||||
vec2(-0.5, -0.5), vec2(0.5, 0.5), vec2(-0.5, 0.5)
|
||||
);
|
||||
|
||||
const vec2 QUAD_UVS[6] = vec2[](
|
||||
vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(1.0, 1.0),
|
||||
vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 1.0)
|
||||
);
|
||||
|
||||
void main() {
|
||||
// 获取四边形顶点
|
||||
vec2 quad_pos = QUAD_VERTICES[gl_VertexIndex % 6];
|
||||
v_uv = QUAD_UVS[gl_VertexIndex % 6];
|
||||
|
||||
// 应用旋转
|
||||
float c = cos(a_rotation);
|
||||
float s = sin(a_rotation);
|
||||
mat2 rot = mat2(c, -s, s, c);
|
||||
quad_pos = rot * quad_pos;
|
||||
|
||||
// 应用大小
|
||||
quad_pos *= a_size;
|
||||
|
||||
// 计算世界位置
|
||||
vec3 world_pos = a_position + vec3(quad_pos, 0.0);
|
||||
|
||||
gl_Position = pc.view_proj * vec4(world_pos, 1.0);
|
||||
v_color = a_color;
|
||||
}
|
||||
```
|
||||
|
||||
**片段着色器:**
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec4 v_color;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
// Set 2: 粒子纹理(可选)
|
||||
layout(set = 2, binding = 0) uniform sampler2D u_particle_texture;
|
||||
|
||||
void main() {
|
||||
// 采样粒子纹理
|
||||
vec4 tex_color = texture(u_particle_texture, v_uv);
|
||||
|
||||
frag_color = tex_color * v_color;
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 简单圆形粒子(无纹理)
|
||||
|
||||
**片段着色器:**
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec4 v_color;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
void main() {
|
||||
// 计算到中心的距离
|
||||
vec2 center = v_uv - 0.5;
|
||||
float dist = length(center) * 2.0;
|
||||
|
||||
// 圆形遮罩,带软边缘
|
||||
float alpha = 1.0 - smoothstep(0.8, 1.0, dist);
|
||||
|
||||
frag_color = v_color;
|
||||
frag_color.a *= alpha * widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Push Constant 使用示例
|
||||
|
||||
Push Constant 完全留给用户,最大 128 bytes,适合高频更新的参数。
|
||||
|
||||
### 5.1 Push Constant 结构设计
|
||||
|
||||
```glsl
|
||||
// 示例 1:简单颜色参数 (32 bytes)
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color; // 16 bytes
|
||||
vec4 secondary; // 16 bytes
|
||||
} pc;
|
||||
|
||||
// 示例 2:渐变参数 (48 bytes)
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color_start; // 16 bytes
|
||||
vec4 color_end; // 16 bytes
|
||||
float angle; // 4 bytes
|
||||
float _pad[3]; // 12 bytes (对齐)
|
||||
} pc;
|
||||
|
||||
// 示例 3:变换矩阵 (64 bytes)
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 transform; // 64 bytes
|
||||
} pc;
|
||||
|
||||
// 示例 4:复杂参数 (128 bytes - 最大)
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 transform; // 64 bytes
|
||||
vec4 color; // 16 bytes
|
||||
vec4 params; // 16 bytes
|
||||
vec4 extra[2]; // 32 bytes
|
||||
} pc;
|
||||
```
|
||||
|
||||
### 5.2 C++ 端 Push Constant 更新
|
||||
|
||||
```cpp
|
||||
// 定义与着色器匹配的结构体
|
||||
struct GradientPushConstants {
|
||||
float color_start[4];
|
||||
float color_end[4];
|
||||
float angle;
|
||||
float _pad[3];
|
||||
};
|
||||
static_assert(sizeof(GradientPushConstants) == 48);
|
||||
|
||||
// 更新 Push Constant
|
||||
void update_gradient(vk::CommandBuffer cmd, vk::PipelineLayout layout) {
|
||||
GradientPushConstants pc{};
|
||||
pc.color_start[0] = 1.0f; pc.color_start[1] = 0.0f;
|
||||
pc.color_start[2] = 0.0f; pc.color_start[3] = 1.0f;
|
||||
pc.color_end[0] = 0.0f; pc.color_end[1] = 0.0f;
|
||||
pc.color_end[2] = 1.0f; pc.color_end[3] = 1.0f;
|
||||
pc.angle = 0.785f; // 45 度
|
||||
|
||||
cmd.pushConstants(
|
||||
layout,
|
||||
vk::ShaderStageFlagBits::eFragment,
|
||||
0,
|
||||
sizeof(pc),
|
||||
&pc
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 用户 UBO 使用示例
|
||||
|
||||
用户 UBO 使用 Set 1,适合不频繁更新的参数。
|
||||
|
||||
### 6.1 UBO 结构设计
|
||||
|
||||
```glsl
|
||||
// Set 1: 用户 UBO
|
||||
layout(set = 1, binding = 0, std140) uniform MyParams {
|
||||
// 注意 std140 布局规则:
|
||||
// - vec4 对齐到 16 bytes
|
||||
// - vec3 对齐到 16 bytes
|
||||
// - vec2 对齐到 8 bytes
|
||||
// - float/int 对齐到 4 bytes
|
||||
// - 数组元素对齐到 16 bytes
|
||||
|
||||
vec4 color; // offset: 0, size: 16
|
||||
vec2 scale; // offset: 16, size: 8
|
||||
float intensity; // offset: 24, size: 4
|
||||
float _pad0; // offset: 28, size: 4 (对齐)
|
||||
vec4 extra_params; // offset: 32, size: 16
|
||||
} params;
|
||||
// 总大小: 48 bytes
|
||||
```
|
||||
|
||||
### 6.2 C++ 端 UBO 结构
|
||||
|
||||
```cpp
|
||||
// 必须与着色器中的布局完全匹配
|
||||
struct alignas(16) MyParamsUBO {
|
||||
float color[4]; // vec4
|
||||
float scale[2]; // vec2
|
||||
float intensity; // float
|
||||
float _pad0; // padding
|
||||
float extra_params[4]; // vec4
|
||||
};
|
||||
static_assert(sizeof(MyParamsUBO) == 48);
|
||||
|
||||
// 更新 UBO
|
||||
void update_params(buffer& ubo) {
|
||||
MyParamsUBO data{};
|
||||
data.color[0] = 1.0f;
|
||||
data.color[1] = 0.5f;
|
||||
data.color[2] = 0.0f;
|
||||
data.color[3] = 1.0f;
|
||||
data.scale[0] = 2.0f;
|
||||
data.scale[1] = 2.0f;
|
||||
data.intensity = 0.8f;
|
||||
|
||||
ubo.write(&data, sizeof(data));
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 多个 UBO 绑定
|
||||
|
||||
```glsl
|
||||
// 可以在 Set 1 定义多个 UBO
|
||||
layout(set = 1, binding = 0, std140) uniform MaterialParams {
|
||||
vec4 albedo;
|
||||
float roughness;
|
||||
float metallic;
|
||||
vec2 _pad;
|
||||
} material;
|
||||
|
||||
layout(set = 1, binding = 1, std140) uniform LightParams {
|
||||
vec4 light_pos;
|
||||
vec4 light_color;
|
||||
float intensity;
|
||||
float _pad[3];
|
||||
} light;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 完整示例:自定义效果控件
|
||||
|
||||
### 7.1 创建波纹效果
|
||||
|
||||
**着色器 (ripple.frag):**
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
// Set 1: 波纹参数
|
||||
layout(set = 1, binding = 0, std140) uniform RippleParams {
|
||||
float time; // 当前时间
|
||||
float frequency; // 波纹频率
|
||||
float amplitude; // 波纹振幅
|
||||
float decay; // 衰减系数
|
||||
} params;
|
||||
|
||||
// Push Constant: 波纹中心和颜色
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color;
|
||||
vec2 center;
|
||||
float _pad[2];
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
// 计算到中心的距离
|
||||
float dist = length(v_local_pos - pc.center);
|
||||
|
||||
// 计算波纹
|
||||
float wave = sin(dist * params.frequency - params.time * 3.0);
|
||||
wave *= params.amplitude * exp(-dist * params.decay);
|
||||
|
||||
// 应用波纹到颜色亮度
|
||||
vec4 color = pc.color;
|
||||
color.rgb *= 1.0 + wave;
|
||||
|
||||
frag_color = color;
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
**C++ 使用:**
|
||||
|
||||
```cpp
|
||||
// UBO 结构
|
||||
struct alignas(16) RippleParamsUBO {
|
||||
float time;
|
||||
float frequency;
|
||||
float amplitude;
|
||||
float decay;
|
||||
};
|
||||
|
||||
// Push Constant 结构
|
||||
struct RipplePushConstants {
|
||||
float color[4];
|
||||
float center[2];
|
||||
float _pad[2];
|
||||
};
|
||||
|
||||
class ripple_widget {
|
||||
public:
|
||||
void set_center(float x, float y) {
|
||||
push_constants_.center[0] = x;
|
||||
push_constants_.center[1] = y;
|
||||
}
|
||||
|
||||
void set_color(float r, float g, float b, float a) {
|
||||
push_constants_.color[0] = r;
|
||||
push_constants_.color[1] = g;
|
||||
push_constants_.color[2] = b;
|
||||
push_constants_.color[3] = a;
|
||||
}
|
||||
|
||||
void update(float delta_time) {
|
||||
params_.time += delta_time;
|
||||
ubo_->write(¶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<buffer> ubo_;
|
||||
vk::PipelineLayout pipeline_layout_;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 相关文档
|
||||
|
||||
- [Shader Widget 架构设计](shader_widget_architecture.md) - 详细的架构设计文档
|
||||
- [ADR-003: 迁移到 GLSL 着色器系统](adr/adr-003-glsl-shader-system.md) - 架构决策记录
|
||||
- [GLSL 着色器系统设计](../plans/glsl_shader_system_design.md) - 详细设计文档
|
||||
598
plans/glsl_shader_system_design.md
Normal file
598
plans/glsl_shader_system_design.md
Normal file
@@ -0,0 +1,598 @@
|
||||
# MIRAI GLSL 着色器系统架构设计
|
||||
|
||||
## 1. 系统架构概述
|
||||
|
||||
### 1.1 设计目标
|
||||
|
||||
1. **从 Slang 迁移到 GLSL**:使用标准 GLSL
|
||||
2. **Push Constant 完全留给用户**:框架不使用
|
||||
3. **框架数据最小化**:只提供 bounds 和 opacity
|
||||
4. **简单约定**:提前约定框架和用户各自使用的资源
|
||||
5. **无 include 机制**:提供模板让用户复制
|
||||
|
||||
### 1.2 四种渲染模式
|
||||
|
||||
| 模式 | 顶点着色器 | 片段着色器 | 说明 |
|
||||
|------|-----------|-----------|------|
|
||||
| Backdrop | 框架提供 | 用户提供 | 后处理效果,框架提供背景纹理 |
|
||||
| Procedural | 框架提供 | 用户提供 | 程序化生成 |
|
||||
| Mask | 框架提供 | 用户提供 | 遮罩效果 |
|
||||
| Custom Mesh | 用户提供 | 用户提供 | 自定义网格渲染 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 资源分配约定
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 资源分配约定表 │
|
||||
├─────────────────┬───────────────────────────────────────────┤
|
||||
│ 资源类型 │ 分配 │
|
||||
├─────────────────┼───────────────────────────────────────────┤
|
||||
│ Set 0 │ 框架专用(用户不要使用) │
|
||||
│ Set 1 │ 用户 UBO │
|
||||
│ Set 2 │ 用户纹理 │
|
||||
│ Push Constant │ 用户专用(最大 128 bytes) │
|
||||
├─────────────────┼───────────────────────────────────────────┤
|
||||
│ 顶点着色器 │ 框架提供(Custom Mesh 除外) │
|
||||
│ 片段着色器 │ 用户提供 │
|
||||
└─────────────────┴───────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Set 0:框架专用(用户不要使用)
|
||||
|
||||
### 3.1 通用模式(Procedural, Mask)
|
||||
|
||||
```glsl
|
||||
// ============================================================
|
||||
// Set 0: 框架专用 - 用户不要在 Set 0 定义任何资源
|
||||
// ============================================================
|
||||
|
||||
// Binding 0: Widget 边界
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds; // (x, y, width, height)
|
||||
float u_opacity; // 0.0 - 1.0
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
// 大小: 32 bytes
|
||||
```
|
||||
|
||||
### 3.2 Backdrop 模式
|
||||
|
||||
```glsl
|
||||
// Binding 0: Widget 边界(同上)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// Binding 1: 背景纹理(框架自动绑定)
|
||||
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 框架内置顶点着色器
|
||||
|
||||
框架为 Backdrop/Procedural/Mask 模式提供内置顶点着色器,用户无需编写。
|
||||
|
||||
### 4.1 内置顶点着色器输出
|
||||
|
||||
框架的顶点着色器会输出以下数据给片段着色器:
|
||||
|
||||
```glsl
|
||||
// 框架顶点着色器输出(用户片段着色器的输入)
|
||||
layout(location = 0) in vec2 v_uv; // UV 坐标 (0-1)
|
||||
layout(location = 1) in vec2 v_local_pos; // 局部坐标 (0-1),相对于 widget bounds
|
||||
```
|
||||
|
||||
### 4.2 框架内置顶点着色器实现(参考)
|
||||
|
||||
```glsl
|
||||
// 框架内部实现,用户无需关心
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 a_position;
|
||||
layout(location = 1) in vec2 a_uv;
|
||||
|
||||
layout(location = 0) out vec2 v_uv;
|
||||
layout(location = 1) out vec2 v_local_pos;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
void main() {
|
||||
// 将顶点位置转换到 NDC
|
||||
vec2 pos = a_position * widget.u_bounds.zw + widget.u_bounds.xy;
|
||||
vec2 ndc = pos * 2.0 - 1.0;
|
||||
gl_Position = vec4(ndc, 0.0, 1.0);
|
||||
|
||||
v_uv = a_uv;
|
||||
v_local_pos = a_position;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 用户片段着色器模板
|
||||
|
||||
### 5.1 Procedural 模式模板
|
||||
|
||||
用户只需编写片段着色器,复制以下模板:
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 框架输入(不要修改)
|
||||
// ============================================================
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用(不要修改)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds; // (x, y, width, height)
|
||||
float u_opacity;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// ============================================================
|
||||
// 用户区域(自由定义)
|
||||
// ============================================================
|
||||
|
||||
// Set 1: 用户 UBO(可选)
|
||||
layout(set = 1, binding = 0, std140) uniform MyParams {
|
||||
float time;
|
||||
// ... 自定义参数
|
||||
} params;
|
||||
|
||||
// Set 2: 用户纹理(可选)
|
||||
// layout(set = 2, binding = 0) uniform sampler2D u_my_texture;
|
||||
|
||||
// Push Constant(可选,最大 128 bytes)
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color;
|
||||
// ... 自定义参数
|
||||
} pc;
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
void main() {
|
||||
// 用户实现...
|
||||
frag_color = pc.color;
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Backdrop 模式模板
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 框架输入(不要修改)
|
||||
// ============================================================
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用(不要修改)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// 背景纹理(框架提供)
|
||||
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
|
||||
|
||||
// ============================================================
|
||||
// 用户区域(自由定义)
|
||||
// ============================================================
|
||||
|
||||
// Set 1: 用户 UBO(可选)
|
||||
layout(set = 1, binding = 0, std140) uniform BlurParams {
|
||||
float radius;
|
||||
int samples;
|
||||
vec2 texel_size;
|
||||
} params;
|
||||
|
||||
// Push Constant(可选)
|
||||
layout(push_constant) uniform PushConstants {
|
||||
// ... 自定义参数
|
||||
} pc;
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
void main() {
|
||||
// 采样背景纹理
|
||||
vec4 color = texture(u_backdrop, v_uv);
|
||||
|
||||
// 用户处理...
|
||||
|
||||
frag_color = color;
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Mask 模式模板
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 框架输入(不要修改)
|
||||
// ============================================================
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用(不要修改)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// ============================================================
|
||||
// 用户区域(自由定义)
|
||||
// ============================================================
|
||||
|
||||
// Push Constant(可选)
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color;
|
||||
float corner_radius;
|
||||
float _pad[3];
|
||||
} pc;
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
void main() {
|
||||
// 计算遮罩(示例:圆角矩形)
|
||||
vec2 size = widget.u_bounds.zw;
|
||||
vec2 p = (v_local_pos - 0.5) * size;
|
||||
vec2 q = abs(p) - size * 0.5 + pc.corner_radius;
|
||||
float d = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - pc.corner_radius;
|
||||
float mask = 1.0 - smoothstep(-1.0, 1.0, d);
|
||||
|
||||
frag_color = pc.color;
|
||||
frag_color.a *= mask * widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 Custom Mesh 模式模板
|
||||
|
||||
Custom Mesh 模式需要用户同时提供顶点和片段着色器:
|
||||
|
||||
**顶点着色器:**
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 用户定义顶点输入
|
||||
// ============================================================
|
||||
layout(location = 0) in vec3 a_position;
|
||||
layout(location = 1) in vec2 a_uv;
|
||||
layout(location = 2) in vec4 a_color;
|
||||
|
||||
layout(location = 0) out vec2 v_uv;
|
||||
layout(location = 1) out vec4 v_color;
|
||||
|
||||
// Set 0: 框架专用(不要修改)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// ============================================================
|
||||
// 用户区域(自由定义)
|
||||
// ============================================================
|
||||
|
||||
// Push Constant
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 mvp;
|
||||
} pc;
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
void main() {
|
||||
gl_Position = pc.mvp * vec4(a_position, 1.0);
|
||||
v_uv = a_uv;
|
||||
v_color = a_color;
|
||||
}
|
||||
```
|
||||
|
||||
**片段着色器:**
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec4 v_color;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用(不要修改)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// ============================================================
|
||||
// 用户区域(自由定义)
|
||||
// ============================================================
|
||||
|
||||
// Set 2: 用户纹理(可选)
|
||||
layout(set = 2, binding = 0) uniform sampler2D u_texture;
|
||||
|
||||
void main() {
|
||||
frag_color = texture(u_texture, v_uv) * v_color;
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 示例着色器
|
||||
|
||||
### 6.1 纯色填充(Procedural)
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
layout(push_constant) uniform PC {
|
||||
vec4 color;
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
frag_color = pc.color;
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 线性渐变(Procedural)
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
layout(push_constant) uniform PC {
|
||||
vec4 color_start;
|
||||
vec4 color_end;
|
||||
float angle;
|
||||
float _pad[3];
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
vec2 dir = vec2(cos(pc.angle), sin(pc.angle));
|
||||
float t = dot(v_local_pos - 0.5, dir) + 0.5;
|
||||
frag_color = mix(pc.color_start, pc.color_end, clamp(t, 0.0, 1.0));
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 高斯模糊(Backdrop)
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
|
||||
|
||||
layout(set = 1, binding = 0, std140) uniform BlurParams {
|
||||
float sigma;
|
||||
int samples;
|
||||
vec2 texel_size;
|
||||
} params;
|
||||
|
||||
void main() {
|
||||
vec4 color = vec4(0.0);
|
||||
float total = 0.0;
|
||||
|
||||
for (int i = -params.samples; i <= params.samples; i++) {
|
||||
for (int j = -params.samples; j <= params.samples; j++) {
|
||||
vec2 offset = vec2(i, j) * params.texel_size;
|
||||
float w = exp(-(float(i*i + j*j)) / (2.0 * params.sigma * params.sigma));
|
||||
color += texture(u_backdrop, v_uv + offset) * w;
|
||||
total += w;
|
||||
}
|
||||
}
|
||||
|
||||
frag_color = color / total;
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 圆角矩形(Mask)
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
layout(push_constant) uniform PC {
|
||||
vec4 color;
|
||||
float radius;
|
||||
float _pad[3];
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
vec2 size = widget.u_bounds.zw;
|
||||
vec2 half_size = size * 0.5;
|
||||
vec2 p = (v_local_pos - 0.5) * size;
|
||||
|
||||
vec2 q = abs(p) - half_size + pc.radius;
|
||||
float d = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - pc.radius;
|
||||
float alpha = 1.0 - smoothstep(-1.0, 1.0, d);
|
||||
|
||||
frag_color = pc.color;
|
||||
frag_color.a *= alpha * widget.u_opacity;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. C++ 端实现(Vulkan HPP)
|
||||
|
||||
### 7.1 框架 UBO 结构
|
||||
|
||||
```cpp
|
||||
namespace mirai {
|
||||
|
||||
struct alignas(16) widget_bounds_ubo {
|
||||
float bounds[4]; // x, y, width, height
|
||||
float opacity;
|
||||
float _pad[3];
|
||||
};
|
||||
static_assert(sizeof(widget_bounds_ubo) == 32);
|
||||
|
||||
} // namespace mirai
|
||||
```
|
||||
|
||||
### 7.2 描述符集布局创建
|
||||
|
||||
```cpp
|
||||
namespace mirai {
|
||||
|
||||
// 通用模式 Set 0 布局
|
||||
inline vk::UniqueDescriptorSetLayout create_set0_layout_common(vk::Device device) {
|
||||
vk::DescriptorSetLayoutBinding binding{
|
||||
0, vk::DescriptorType::eUniformBuffer, 1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment
|
||||
};
|
||||
return device.createDescriptorSetLayoutUnique({{}, 1, &binding});
|
||||
}
|
||||
|
||||
// Backdrop 模式 Set 0 布局
|
||||
inline vk::UniqueDescriptorSetLayout create_set0_layout_backdrop(vk::Device device) {
|
||||
std::array<vk::DescriptorSetLayoutBinding, 2> bindings = {{
|
||||
{0, vk::DescriptorType::eUniformBuffer, 1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1,
|
||||
vk::ShaderStageFlagBits::eFragment}
|
||||
}};
|
||||
return device.createDescriptorSetLayoutUnique({{}, bindings});
|
||||
}
|
||||
|
||||
} // namespace mirai
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 编译系统
|
||||
|
||||
### 8.1 编译流程
|
||||
|
||||
```
|
||||
用户 GLSL 片段着色器 → glslangValidator → SPIR-V → spirv-cross(反射)
|
||||
```
|
||||
|
||||
### 8.2 CMake 集成
|
||||
|
||||
```cmake
|
||||
function(compile_glsl_shader INPUT OUTPUT)
|
||||
add_custom_command(
|
||||
OUTPUT ${OUTPUT}
|
||||
COMMAND glslangValidator -V ${INPUT} -o ${OUTPUT}
|
||||
DEPENDS ${INPUT}
|
||||
COMMENT "Compiling ${INPUT}"
|
||||
)
|
||||
endfunction()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 总结
|
||||
|
||||
### 资源分配
|
||||
|
||||
| 资源 | 归属 | 说明 |
|
||||
|------|------|------|
|
||||
| Set 0 | 框架 | WidgetBounds + Backdrop纹理 |
|
||||
| Set 1 | 用户 | 用户 UBO |
|
||||
| Set 2 | 用户 | 用户纹理 |
|
||||
| Push Constant | 用户 | 最大 128 bytes |
|
||||
| 顶点着色器 | 框架 | Custom Mesh 除外 |
|
||||
| 片段着色器 | 用户 | 所有模式 |
|
||||
|
||||
### 框架提供的数据
|
||||
|
||||
| 数据 | 位置 | 类型 |
|
||||
|------|------|------|
|
||||
| u_bounds | Set 0, Binding 0 | vec4 |
|
||||
| u_opacity | Set 0, Binding 0 | float |
|
||||
| u_backdrop | Set 0, Binding 1 | sampler2D (仅 Backdrop) |
|
||||
|
||||
### 框架顶点着色器输出
|
||||
|
||||
| 输出 | location | 类型 | 说明 |
|
||||
|------|----------|------|------|
|
||||
| v_uv | 0 | vec2 | UV 坐标 (0-1) |
|
||||
| v_local_pos | 1 | vec2 | 局部坐标 (0-1) |
|
||||
1261
plans/shader_codegen_design.md
Normal file
1261
plans/shader_codegen_design.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,317 +0,0 @@
|
||||
# Vulkan C API 到 vulkan.hpp 迁移计划
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本文档分析了 mirai 项目中 Vulkan C API 的使用情况,并提供迁移到 vulkan.hpp (Vulkan C++ 绑定) 的详细计划。
|
||||
|
||||
## 2. 需要迁移的文件
|
||||
|
||||
### 2.1 src/render/ 目录
|
||||
|
||||
| 文件 | Vulkan 类型使用情况 | 迁移优先级 |
|
||||
|------|-------------------|-----------|
|
||||
| [`vulkan_types.hpp`](../src/render/vulkan_types.hpp) | VkResult, VkSurfaceCapabilitiesKHR, VkSurfaceFormatKHR, VkPresentModeKHR, VkExtent2D, VkPhysicalDevice, VkPhysicalDeviceProperties, VkPhysicalDeviceFeatures, VkPhysicalDeviceVulkan11/12/13Features, VkPhysicalDeviceMemoryProperties, VkFormat, VkColorSpaceKHR | **高** - 核心类型定义 |
|
||||
| [`vulkan_instance.hpp`](../src/render/vulkan_instance.hpp) | VkInstance, VkDebugUtilsMessengerEXT, VkExtensionProperties, VkLayerProperties, VkSurfaceKHR | **高** - 实例管理 |
|
||||
| [`vulkan_device.hpp`](../src/render/vulkan_device.hpp) | VkDevice, VkPhysicalDevice, VkQueue, VkMemoryPropertyFlags, VkFormat, VkImageTiling, VkFormatFeatureFlags | **高** - 设备管理 |
|
||||
| [`swapchain.hpp`](../src/render/swapchain.hpp) | VkSwapchainKHR, VkImage, VkImageView, VkSurfaceFormatKHR, VkExtent2D, VkPresentModeKHR, VkSemaphore, VkFence | **高** - 交换链 |
|
||||
| [`command_buffer.hpp`](../src/render/command_buffer.hpp) | VkCommandPool, VkCommandBuffer, VkCommandBufferLevel, VkCommandBufferUsageFlags, VkDependencyInfo, VkPipeline, VkBuffer, VkIndexType, VkDescriptorSet, VkPipelineLayout, VkRect2D, VkExtent2D | **中** - 命令缓冲 |
|
||||
| [`pipeline.hpp`](../src/render/pipeline.hpp) | VkPipelineLayout, VkPipeline, VkShaderModule, VkFormat, VkVertexInputRate, VkPrimitiveTopology, VkPolygonMode, VkCullModeFlags, VkFrontFace, VkSampleCountFlagBits, VkCompareOp, VkStencilOpState, VkBlendFactor, VkBlendOp, VkColorComponentFlags, VkDynamicState, VkDescriptorSetLayout, VkPushConstantRange | **中** - 管线 |
|
||||
| [`frame_sync.hpp`](../src/render/frame_sync.hpp) | VkSemaphore, VkFence | **低** |
|
||||
| [`render_pass.hpp`](../src/render/render_pass.hpp) | VkRenderPass, VkFramebuffer | **低** (可能已使用 Dynamic Rendering) |
|
||||
| [`batch.hpp`](../src/render/batch.hpp) | 可能使用 Vulkan 类型 | **低** |
|
||||
| [`renderer.hpp`](../src/render/renderer.hpp) | 组合使用其他模块 | **低** |
|
||||
|
||||
### 2.2 src/resource/ 目录
|
||||
|
||||
| 文件 | Vulkan 类型使用情况 | 迁移优先级 |
|
||||
|------|-------------------|-----------|
|
||||
| [`resource_types.hpp`](../src/resource/resource_types.hpp) | 可能定义 Vulkan 相关枚举映射 | **中** |
|
||||
| [`allocator.hpp`](../src/resource/allocator.hpp) | VmaAllocator, VkBuffer, VkImage, VmaAllocation | **高** - VMA 集成 |
|
||||
| [`buffer.hpp`](../src/resource/buffer.hpp) | VkBuffer, VkDeviceAddress, VkCommandBuffer, VkDescriptorBufferInfo, VkDeviceSize | **中** |
|
||||
| [`texture.hpp`](../src/resource/texture.hpp) | VkImage, VkImageSubresourceRange, VkCommandBuffer | **中** |
|
||||
| [`texture_view.hpp`](../src/resource/texture_view.hpp) | VkImageView | **中** |
|
||||
| [`sampler.hpp`](../src/resource/sampler.hpp) | VkSampler | **低** |
|
||||
| [`descriptor_pool.hpp`](../src/resource/descriptor_pool.hpp) | VkDescriptorPool, VkDescriptorSet, VkDescriptorSetLayout, VkDescriptorBufferInfo, VkDescriptorImageInfo, VkBufferView, VkShaderStageFlags, VkSampler, VkImageLayout | **中** |
|
||||
|
||||
### 2.3 src/shader/ 目录
|
||||
|
||||
| 文件 | Vulkan 类型使用情况 | 迁移优先级 |
|
||||
|------|-------------------|-----------|
|
||||
| [`shader_module.hpp`](../src/shader/shader_module.hpp) | VkShaderModule, VkDevice | **中** |
|
||||
| [`shader_types.hpp`](../src/shader/shader_types.hpp) | 可能包含 Vulkan 类型映射 | **中** |
|
||||
| [`shader_reflection.hpp`](../src/shader/shader_reflection.hpp) | 可能使用 Vulkan 描述符类型 | **低** |
|
||||
| [`shader_program.hpp`](../src/shader/shader_program.hpp) | 组合 shader_module | **低** |
|
||||
|
||||
## 3. Vulkan 类型使用统计
|
||||
|
||||
### 3.1 核心句柄类型
|
||||
- `VkInstance` - 实例
|
||||
- `VkPhysicalDevice` - 物理设备
|
||||
- `VkDevice` - 逻辑设备
|
||||
- `VkQueue` - 队列
|
||||
- `VkSurfaceKHR` - 表面
|
||||
- `VkSwapchainKHR` - 交换链
|
||||
- `VkCommandPool` - 命令池
|
||||
- `VkCommandBuffer` - 命令缓冲
|
||||
- `VkBuffer` - 缓冲
|
||||
- `VkImage` - 图像
|
||||
- `VkImageView` - 图像视图
|
||||
- `VkSampler` - 采样器
|
||||
- `VkPipeline` - 管线
|
||||
- `VkPipelineLayout` - 管线布局
|
||||
- `VkDescriptorPool` - 描述符池
|
||||
- `VkDescriptorSet` - 描述符集
|
||||
- `VkDescriptorSetLayout` - 描述符集布局
|
||||
- `VkShaderModule` - 着色器模块
|
||||
- `VkSemaphore` - 信号量
|
||||
- `VkFence` - 栅栏
|
||||
- `VkDebugUtilsMessengerEXT` - 调试信使
|
||||
|
||||
### 3.2 结构体类型
|
||||
- `VkPhysicalDeviceProperties`
|
||||
- `VkPhysicalDeviceFeatures`
|
||||
- `VkPhysicalDeviceVulkan11Features`
|
||||
- `VkPhysicalDeviceVulkan12Features`
|
||||
- `VkPhysicalDeviceVulkan13Features`
|
||||
- `VkPhysicalDeviceMemoryProperties`
|
||||
- `VkSurfaceCapabilitiesKHR`
|
||||
- `VkSurfaceFormatKHR`
|
||||
- `VkExtent2D`
|
||||
- `VkRect2D`
|
||||
- `VkDescriptorBufferInfo`
|
||||
- `VkDescriptorImageInfo`
|
||||
- `VkImageSubresourceRange`
|
||||
- `VkDependencyInfo`
|
||||
- `VkPushConstantRange`
|
||||
- `VkStencilOpState`
|
||||
- `VkPipelineColorBlendAttachmentState`
|
||||
- `VkExtensionProperties`
|
||||
- `VkLayerProperties`
|
||||
|
||||
### 3.3 枚举类型
|
||||
- `VkResult`
|
||||
- `VkFormat`
|
||||
- `VkColorSpaceKHR`
|
||||
- `VkPresentModeKHR`
|
||||
- `VkImageLayout`
|
||||
- `VkPrimitiveTopology`
|
||||
- `VkPolygonMode`
|
||||
- `VkCullModeFlags`
|
||||
- `VkFrontFace`
|
||||
- `VkCompareOp`
|
||||
- `VkBlendFactor`
|
||||
- `VkBlendOp`
|
||||
- `VkDynamicState`
|
||||
- `VkShaderStageFlags`
|
||||
- `VkVertexInputRate`
|
||||
- `VkSampleCountFlagBits`
|
||||
- `VkIndexType`
|
||||
- `VkCommandBufferLevel`
|
||||
- `VkImageTiling`
|
||||
- `VkMemoryPropertyFlags`
|
||||
- `VkFormatFeatureFlags`
|
||||
- `VkColorComponentFlags`
|
||||
|
||||
## 4. 迁移策略
|
||||
|
||||
### 4.1 是否需要包装层
|
||||
|
||||
**建议:不需要创建单独的 vulkan_wrapper.hpp**
|
||||
|
||||
理由:
|
||||
1. vulkan.hpp 本身已经是 C API 的 C++ 包装
|
||||
2. 项目已有良好的抽象层(vulkan_instance, vulkan_device 等类)
|
||||
3. 直接在现有类中使用 vulkan.hpp 类型即可
|
||||
|
||||
### 4.2 迁移方式
|
||||
|
||||
**推荐:渐进式迁移**
|
||||
|
||||
```cpp
|
||||
// 迁移前
|
||||
#include <vulkan/vulkan.h>
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
|
||||
// 迁移后
|
||||
#include <vulkan/vulkan.hpp>
|
||||
vk::Device device; // 或 vk::UniqueDevice 用于 RAII
|
||||
```
|
||||
|
||||
### 4.3 关键转换对照表
|
||||
|
||||
| C API | vulkan.hpp | 备注 |
|
||||
|-------|-----------|------|
|
||||
| `VkInstance` | `vk::Instance` | |
|
||||
| `VkDevice` | `vk::Device` / `vk::UniqueDevice` | UniqueDevice 自动管理生命周期 |
|
||||
| `VkPhysicalDevice` | `vk::PhysicalDevice` | |
|
||||
| `VkQueue` | `vk::Queue` | |
|
||||
| `VkCommandBuffer` | `vk::CommandBuffer` | |
|
||||
| `VkBuffer` | `vk::Buffer` / `vk::UniqueBuffer` | |
|
||||
| `VkImage` | `vk::Image` / `vk::UniqueImage` | |
|
||||
| `VK_NULL_HANDLE` | `nullptr` 或默认构造 | |
|
||||
| `VK_SUCCESS` | `vk::Result::eSuccess` | |
|
||||
| `vkCreateXxx()` | `device.createXxx()` | 成员函数 |
|
||||
| `vkDestroyXxx()` | `device.destroyXxx()` | 或使用 UniqueXxx 自动销毁 |
|
||||
| `VkResult` | `vk::Result` 或异常 | |
|
||||
|
||||
### 4.4 错误处理策略
|
||||
|
||||
vulkan.hpp 支持两种错误处理方式:
|
||||
|
||||
1. **异常模式**(默认):
|
||||
```cpp
|
||||
try {
|
||||
auto device = instance.createDevice(createInfo);
|
||||
} catch (vk::SystemError& e) {
|
||||
// 处理错误
|
||||
}
|
||||
```
|
||||
|
||||
2. **返回值模式**:
|
||||
```cpp
|
||||
#define VULKAN_HPP_NO_EXCEPTIONS
|
||||
auto [result, device] = instance.createDevice(createInfo);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
// 处理错误
|
||||
}
|
||||
```
|
||||
|
||||
**建议:使用返回值模式**,与项目现有的 `result<T>` 错误处理风格一致。
|
||||
|
||||
## 5. 迁移顺序
|
||||
|
||||
### 阶段 1:基础设施(高优先级)
|
||||
1. [`vulkan_types.hpp`](../src/render/vulkan_types.hpp) - 更新类型定义
|
||||
2. [`vulkan_instance.hpp`](../src/render/vulkan_instance.hpp) / `.cpp`
|
||||
3. [`vulkan_device.hpp`](../src/render/vulkan_device.hpp) / `.cpp`
|
||||
4. [`allocator.hpp`](../src/resource/allocator.hpp) / `.cpp` - VMA 集成
|
||||
|
||||
### 阶段 2:资源管理(中优先级)
|
||||
5. [`swapchain.hpp`](../src/render/swapchain.hpp) / `.cpp`
|
||||
6. [`buffer.hpp`](../src/resource/buffer.hpp) / `.cpp`
|
||||
7. [`texture.hpp`](../src/resource/texture.hpp) / `.cpp`
|
||||
8. [`texture_view.hpp`](../src/resource/texture_view.hpp) / `.cpp`
|
||||
9. [`descriptor_pool.hpp`](../src/resource/descriptor_pool.hpp) / `.cpp`
|
||||
|
||||
### 阶段 3:渲染管线(中优先级)
|
||||
10. [`command_buffer.hpp`](../src/render/command_buffer.hpp) / `.cpp`
|
||||
11. [`pipeline.hpp`](../src/render/pipeline.hpp) / `.cpp`
|
||||
12. [`shader_module.hpp`](../src/shader/shader_module.hpp) / `.cpp`
|
||||
|
||||
### 阶段 4:其他模块(低优先级)
|
||||
13. [`frame_sync.hpp`](../src/render/frame_sync.hpp) / `.cpp`
|
||||
14. [`sampler.hpp`](../src/resource/sampler.hpp) / `.cpp`
|
||||
15. 其他 shader 相关文件
|
||||
16. [`renderer.hpp`](../src/render/renderer.hpp) / `.cpp`
|
||||
|
||||
## 6. 兼容性注意事项
|
||||
|
||||
### 6.1 VMA (Vulkan Memory Allocator) 集成
|
||||
|
||||
VMA 使用 C API,需要特殊处理:
|
||||
|
||||
```cpp
|
||||
// 从 vulkan.hpp 类型获取 C 句柄
|
||||
vk::Device device;
|
||||
VkDevice c_device = static_cast<VkDevice>(device);
|
||||
// 或
|
||||
VkDevice c_device = *device;
|
||||
|
||||
// 将 C 句柄转换为 vulkan.hpp 类型
|
||||
VkBuffer c_buffer = ...;
|
||||
vk::Buffer buffer(c_buffer);
|
||||
```
|
||||
|
||||
### 6.2 与现有代码的兼容
|
||||
|
||||
- vulkan.hpp 类型可以隐式转换为 C 类型
|
||||
- C 类型需要显式构造为 vulkan.hpp 类型
|
||||
- 建议在迁移期间保持 getter 方法返回 C 类型以保持兼容
|
||||
|
||||
### 6.3 编译器要求
|
||||
|
||||
- 需要 C++17 或更高版本(项目已使用 C++20)
|
||||
- 需要定义 `VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1` 使用动态加载
|
||||
|
||||
### 6.4 CMake 配置
|
||||
|
||||
```cmake
|
||||
# vcpkg.json 添加
|
||||
{
|
||||
"dependencies": [
|
||||
"vulkan",
|
||||
"vulkan-hpp"
|
||||
]
|
||||
}
|
||||
|
||||
# 或直接使用 Vulkan SDK 自带的 vulkan.hpp
|
||||
find_package(Vulkan REQUIRED)
|
||||
target_link_libraries(${PROJECT_NAME} Vulkan::Vulkan)
|
||||
```
|
||||
|
||||
## 7. 代码示例
|
||||
|
||||
### 7.1 迁移前后对比
|
||||
|
||||
**迁移前 (vulkan_instance.cpp):**
|
||||
```cpp
|
||||
VkInstanceCreateInfo create_info{};
|
||||
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||
create_info.pApplicationInfo = &app_info;
|
||||
create_info.enabledExtensionCount = static_cast<u32>(extensions.size());
|
||||
create_info.ppEnabledExtensionNames = extensions.data();
|
||||
|
||||
VkResult result = vkCreateInstance(&create_info, nullptr, &instance_);
|
||||
if (result != VK_SUCCESS) {
|
||||
return error("Failed to create Vulkan instance");
|
||||
}
|
||||
```
|
||||
|
||||
**迁移后:**
|
||||
```cpp
|
||||
vk::InstanceCreateInfo create_info{};
|
||||
create_info.setPApplicationInfo(&app_info)
|
||||
.setEnabledExtensionCount(static_cast<u32>(extensions.size()))
|
||||
.setPpEnabledExtensionNames(extensions.data());
|
||||
|
||||
auto [result, instance] = vk::createInstance(create_info);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
return error("Failed to create Vulkan instance");
|
||||
}
|
||||
instance_ = instance;
|
||||
```
|
||||
|
||||
### 7.2 使用 UniqueHandle 简化资源管理
|
||||
|
||||
```cpp
|
||||
// 使用 UniqueDevice 自动管理设备生命周期
|
||||
vk::UniqueDevice device = physicalDevice.createDeviceUnique(createInfo);
|
||||
|
||||
// 设备会在 unique_ptr 销毁时自动调用 vkDestroyDevice
|
||||
```
|
||||
|
||||
## 8. 测试策略
|
||||
|
||||
1. **单元测试**:每个迁移的模块需要通过现有单元测试
|
||||
2. **集成测试**:确保渲染管线正常工作
|
||||
3. **性能测试**:验证没有性能回退(vulkan.hpp 是零开销抽象)
|
||||
|
||||
## 9. 风险评估
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|---------|
|
||||
| VMA 兼容性问题 | 中 | 在 allocator 层做类型转换 |
|
||||
| 编译时间增加 | 低 | vulkan.hpp 主要是头文件,可能增加编译时间 |
|
||||
| 调试困难 | 低 | 使用 Vulkan Validation Layers |
|
||||
| 团队学习成本 | 低 | vulkan.hpp 语法直观,学习曲线平缓 |
|
||||
|
||||
## 10. 总结
|
||||
|
||||
迁移到 vulkan.hpp 将带来以下好处:
|
||||
- 更安全的类型系统
|
||||
- 更简洁的代码
|
||||
- 更好的 IDE 支持(自动补全)
|
||||
- 可选的 RAII 资源管理
|
||||
- 更现代的 C++ 风格
|
||||
|
||||
建议采用渐进式迁移策略,从基础设施层开始,逐步扩展到其他模块。
|
||||
@@ -1,23 +1,15 @@
|
||||
# ================================================================================================
|
||||
# MIRAI Framework - App 模块
|
||||
# 描述: 示例应用程序
|
||||
# 描述: 演示应用程序
|
||||
# ================================================================================================
|
||||
|
||||
project(mirai_demo)
|
||||
simple_executable()
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
mirai_core
|
||||
mirai_threading
|
||||
mirai_window
|
||||
mirai_resource
|
||||
mirai_shader
|
||||
mirai_demo_lib
|
||||
mirai_render
|
||||
mirai_text
|
||||
mirai_input
|
||||
mirai_reactive
|
||||
mirai_loop
|
||||
mirai_ui
|
||||
mirai_debug
|
||||
)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||
|
||||
@@ -1,19 +1,50 @@
|
||||
/**
|
||||
* @file main.cpp
|
||||
* @brief MIRAI 框架示例应用
|
||||
* @brief MIRAI 框架演示应用入口
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 本文件是 MIRAI 框架的示例应用入口点
|
||||
* 本文件是 MIRAI 框架演示应用的入口点
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include "demo_app.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::cout << "MIRAI Framework v0.1.0" << std::endl;
|
||||
std::cout << "=====================" << std::endl;
|
||||
std::cout << "This is a placeholder demo application." << std::endl;
|
||||
std::cout << "Full implementation coming in later phases." << std::endl;
|
||||
int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) {
|
||||
// 初始化日志系统
|
||||
mirai::logger_config log_config;
|
||||
log_config.name = "mirai_demo";
|
||||
log_config.level = mirai::log_level::debug;
|
||||
log_config.console_enabled = true;
|
||||
mirai::init_logger(log_config);
|
||||
|
||||
MIRAI_LOG_INFO("========================================");
|
||||
MIRAI_LOG_INFO("MIRAI Framework Demo v0.1.0");
|
||||
MIRAI_LOG_INFO("========================================");
|
||||
|
||||
// 创建演示应用配置
|
||||
mirai::demo::demo_window_config config;
|
||||
config.width = 1280;
|
||||
config.height = 720;
|
||||
config.title = "MIRAI Demo";
|
||||
config.resizable = true;
|
||||
config.vsync = true;
|
||||
config.high_dpi = true;
|
||||
|
||||
// 创建并运行演示应用
|
||||
mirai::demo::demo_app app(config);
|
||||
|
||||
if (!app.initialize()) {
|
||||
MIRAI_LOG_ERROR("Failed to initialize demo application");
|
||||
mirai::shutdown_logger();
|
||||
return 1;
|
||||
}
|
||||
|
||||
app.run();
|
||||
app.shutdown();
|
||||
|
||||
MIRAI_LOG_INFO("Demo application exited normally");
|
||||
mirai::shutdown_logger();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -23,440 +23,433 @@
|
||||
#include "types/types.hpp"
|
||||
|
||||
namespace mirai {
|
||||
// ================================================================================================
|
||||
// 前置声明
|
||||
// ================================================================================================
|
||||
|
||||
// ================================================================================================
|
||||
// 前置声明
|
||||
// ================================================================================================
|
||||
class object;
|
||||
class object_registry;
|
||||
|
||||
class object;
|
||||
class object_registry;
|
||||
// make_obj 辅助结构体前置声明
|
||||
struct object_factory;
|
||||
|
||||
// make_obj 辅助结构体前置声明
|
||||
struct object_factory;
|
||||
// ================================================================================================
|
||||
// 对象 ID 类型
|
||||
// ================================================================================================
|
||||
|
||||
// ================================================================================================
|
||||
// 对象 ID 类型
|
||||
// ================================================================================================
|
||||
/**
|
||||
* @brief 对象唯一标识符类型
|
||||
*
|
||||
* 每个 object 实例都有一个唯一的 ID,用于调试和追踪
|
||||
*/
|
||||
using object_id = u64;
|
||||
|
||||
/**
|
||||
* @brief 对象唯一标识符类型
|
||||
*
|
||||
* 每个 object 实例都有一个唯一的 ID,用于调试和追踪
|
||||
*/
|
||||
using object_id = u64;
|
||||
/**
|
||||
* @brief 无效的对象 ID
|
||||
*/
|
||||
constexpr object_id invalid_object_id = 0;
|
||||
|
||||
/**
|
||||
* @brief 无效的对象 ID
|
||||
*/
|
||||
constexpr object_id invalid_object_id = 0;
|
||||
// ================================================================================================
|
||||
// 类型信息
|
||||
// ================================================================================================
|
||||
|
||||
// ================================================================================================
|
||||
// 类型信息
|
||||
// ================================================================================================
|
||||
/**
|
||||
* @brief 类型信息结构
|
||||
*
|
||||
* 提供运行时类型信息,作为 RTTI 的替代方案
|
||||
*/
|
||||
struct type_info {
|
||||
/// 类型名称
|
||||
std::string_view name;
|
||||
|
||||
/**
|
||||
* @brief 类型信息结构
|
||||
*
|
||||
* 提供运行时类型信息,作为 RTTI 的替代方案
|
||||
*/
|
||||
struct type_info {
|
||||
/// 类型名称
|
||||
std::string_view name;
|
||||
|
||||
/// 类型哈希值(用于快速比较)
|
||||
size_type hash;
|
||||
|
||||
/// 父类型信息(如果有)
|
||||
const type_info* parent;
|
||||
|
||||
/**
|
||||
* @brief 检查是否与另一个类型相同
|
||||
* @param other 另一个类型信息
|
||||
* @return 如果相同返回 true
|
||||
*/
|
||||
[[nodiscard]] constexpr bool operator==(const type_info& other) const noexcept {
|
||||
return hash == other.hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否与另一个类型不同
|
||||
* @param other 另一个类型信息
|
||||
* @return 如果不同返回 true
|
||||
*/
|
||||
[[nodiscard]] constexpr bool operator!=(const type_info& other) const noexcept {
|
||||
return hash != other.hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查此类型是否派生自另一个类型
|
||||
* @param base 基类类型信息
|
||||
* @return 如果是派生类返回 true
|
||||
*/
|
||||
[[nodiscard]] bool is_derived_from(const type_info& base) const noexcept {
|
||||
const type_info* current = this;
|
||||
while (current != nullptr) {
|
||||
if (*current == base) {
|
||||
return true;
|
||||
}
|
||||
current = current->parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
/// 类型哈希值(用于快速比较)
|
||||
size_type hash;
|
||||
|
||||
namespace detail {
|
||||
/// 父类型信息(如果有)
|
||||
const type_info* parent;
|
||||
|
||||
/**
|
||||
* @brief 编译时计算字符串哈希值
|
||||
* @param str 字符串
|
||||
* @return 哈希值
|
||||
*/
|
||||
constexpr size_type compile_time_hash(std::string_view str) noexcept {
|
||||
size_type hash = 14695981039346656037ULL;
|
||||
for (char c : str) {
|
||||
hash ^= static_cast<size_type>(c);
|
||||
hash *= 1099511628211ULL;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
/**
|
||||
* @brief 检查是否与另一个类型相同
|
||||
* @param other 另一个类型信息
|
||||
* @return 如果相同返回 true
|
||||
*/
|
||||
[[nodiscard]] constexpr bool operator==(const type_info& other) const noexcept {
|
||||
return hash == other.hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取类型名称
|
||||
* @tparam T 类型
|
||||
* @return 类型名称字符串
|
||||
*/
|
||||
template<typename T>
|
||||
consteval std::string_view get_type_name() noexcept {
|
||||
#if defined(__clang__)
|
||||
constexpr std::string_view prefix = "[T = ";
|
||||
constexpr std::string_view suffix = "]";
|
||||
constexpr std::string_view function = __PRETTY_FUNCTION__;
|
||||
#elif defined(__GNUC__)
|
||||
constexpr std::string_view prefix = "with T = ";
|
||||
constexpr std::string_view suffix = "]";
|
||||
constexpr std::string_view function = __PRETTY_FUNCTION__;
|
||||
#elif defined(_MSC_VER)
|
||||
constexpr std::string_view prefix = "get_type_name<";
|
||||
constexpr std::string_view suffix = ">(void)";
|
||||
constexpr std::string_view function = __FUNCSIG__;
|
||||
#else
|
||||
return typeid(T).name();
|
||||
#endif
|
||||
|
||||
const auto start = function.find(prefix) + prefix.size();
|
||||
const auto end = function.rfind(suffix);
|
||||
return function.substr(start, end - start);
|
||||
}
|
||||
/**
|
||||
* @brief 检查是否与另一个类型不同
|
||||
* @param other 另一个类型信息
|
||||
* @return 如果不同返回 true
|
||||
*/
|
||||
[[nodiscard]] constexpr bool operator!=(const type_info& other) const noexcept {
|
||||
return hash != other.hash;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
/**
|
||||
* @brief 检查此类型是否派生自另一个类型
|
||||
* @param base 基类类型信息
|
||||
* @return 如果是派生类返回 true
|
||||
*/
|
||||
[[nodiscard]] bool is_derived_from(const type_info& base) const noexcept {
|
||||
const type_info* current = this;
|
||||
while (current != nullptr) {
|
||||
if (*current == base) {
|
||||
return true;
|
||||
}
|
||||
current = current->parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 获取类型的类型信息
|
||||
* @tparam T 类型
|
||||
* @return 类型信息的常量引用
|
||||
*/
|
||||
template<typename T>
|
||||
[[nodiscard]] const type_info& get_type_info() noexcept {
|
||||
static const type_info info{
|
||||
detail::get_type_name<T>(),
|
||||
detail::compile_time_hash(detail::get_type_name<T>()),
|
||||
nullptr
|
||||
};
|
||||
return info;
|
||||
}
|
||||
namespace detail {
|
||||
/**
|
||||
* @brief 编译时计算字符串哈希值
|
||||
* @param str 字符串
|
||||
* @return 哈希值
|
||||
*/
|
||||
constexpr size_type compile_time_hash(std::string_view str) noexcept {
|
||||
size_type hash = 14695981039346656037ULL;
|
||||
for (char c : str) {
|
||||
hash ^= static_cast<size_type>(c);
|
||||
hash *= 1099511628211ULL;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取带父类的类型信息
|
||||
* @tparam T 类型
|
||||
* @tparam Parent 父类类型
|
||||
* @return 类型信息的常量引用
|
||||
*/
|
||||
template<typename T, typename Parent>
|
||||
[[nodiscard]] const type_info& get_type_info_with_parent() noexcept {
|
||||
static const type_info info{
|
||||
detail::get_type_name<T>(),
|
||||
detail::compile_time_hash(detail::get_type_name<T>()),
|
||||
&get_type_info<Parent>()
|
||||
};
|
||||
return info;
|
||||
}
|
||||
/**
|
||||
* @brief 获取类型名称
|
||||
* @tparam T 类型
|
||||
* @return 类型名称字符串
|
||||
*/
|
||||
template <typename T>
|
||||
consteval std::string_view get_type_name() noexcept {
|
||||
#if defined(__clang__)
|
||||
constexpr std::string_view prefix = "[T = ";
|
||||
constexpr std::string_view suffix = "]";
|
||||
constexpr std::string_view function = __PRETTY_FUNCTION__;
|
||||
#elif defined(__GNUC__)
|
||||
constexpr std::string_view prefix = "with T = ";
|
||||
constexpr std::string_view suffix = "]";
|
||||
constexpr std::string_view function = __PRETTY_FUNCTION__;
|
||||
#elif defined(_MSC_VER)
|
||||
constexpr std::string_view prefix = "get_type_name<";
|
||||
constexpr std::string_view suffix = ">(void)";
|
||||
constexpr std::string_view function = __FUNCSIG__;
|
||||
#else
|
||||
return typeid(T).name();
|
||||
#endif
|
||||
|
||||
// ================================================================================================
|
||||
// 对象基类
|
||||
// ================================================================================================
|
||||
const auto start = function.find(prefix) + prefix.size();
|
||||
const auto end = function.rfind(suffix);
|
||||
return function.substr(start, end - start);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* @brief 对象基类
|
||||
*
|
||||
* 所有 MIRAI 框架对象的基类,提供:
|
||||
* - 共享指针支持(通过 enable_shared_from_this)
|
||||
* - 类型安全的指针转换
|
||||
* - 唯一对象标识符
|
||||
* - 运行时类型信息
|
||||
*
|
||||
* @note 对象必须通过 make_object<T>() 工厂函数创建
|
||||
* @note 对象禁止拷贝,但允许移动
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* class my_widget : public object {
|
||||
* public:
|
||||
* MIRAI_OBJECT_TYPE_INFO(my_widget, object)
|
||||
*
|
||||
* my_widget(std::string name) : name_(std::move(name)) {}
|
||||
*
|
||||
* protected:
|
||||
* void on_created() override {
|
||||
* // 初始化逻辑
|
||||
* }
|
||||
*
|
||||
* private:
|
||||
* std::string name_;
|
||||
* };
|
||||
*
|
||||
* auto widget = make_object<my_widget>("test");
|
||||
* auto shared = widget->shared_from_this_as<my_widget>();
|
||||
* @endcode
|
||||
*/
|
||||
class object : public std::enable_shared_from_this<object> {
|
||||
public:
|
||||
/// 共享指针类型别名
|
||||
using ptr = std::shared_ptr<object>;
|
||||
|
||||
/// 弱指针类型别名
|
||||
using weak_ptr = std::weak_ptr<object>;
|
||||
|
||||
/**
|
||||
* @brief 虚析构函数
|
||||
*/
|
||||
virtual ~object();
|
||||
|
||||
// 禁止拷贝
|
||||
object(const object&) = delete;
|
||||
object& operator=(const object&) = delete;
|
||||
|
||||
// 禁止移动(因为只能通过智能指针管理)
|
||||
object(object&&) = delete;
|
||||
object& operator=(object&&) = delete;
|
||||
|
||||
// ============================================================================================
|
||||
// 类型信息
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取对象的类型信息
|
||||
* @return 类型信息的常量引用
|
||||
*/
|
||||
[[nodiscard]] virtual const type_info& get_type() const noexcept {
|
||||
return get_type_info<object>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取对象的类型名称
|
||||
* @return 类型名称字符串
|
||||
*/
|
||||
[[nodiscard]] std::string_view type_name() const noexcept {
|
||||
return get_type().name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查对象是否为指定类型
|
||||
* @tparam T 要检查的类型
|
||||
* @return 如果是指定类型返回 true
|
||||
*/
|
||||
template<typename T> requires std::derived_from<T, object>
|
||||
[[nodiscard]] bool is() const noexcept {
|
||||
return get_type().is_derived_from(get_type_info<T>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 安全地将对象转换为指定类型
|
||||
* @tparam T 目标类型
|
||||
* @return 如果转换成功返回指向对象的指针,否则返回 nullptr
|
||||
*/
|
||||
template<typename T>
|
||||
requires std::derived_from<T, object>
|
||||
[[nodiscard]] T* as() noexcept {
|
||||
if (is<T>()) {
|
||||
return static_cast<T*>(this);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 安全地将对象转换为指定类型(const 版本)
|
||||
* @tparam T 目标类型
|
||||
* @return 如果转换成功返回指向对象的常量指针,否则返回 nullptr
|
||||
*/
|
||||
template<typename T>
|
||||
requires std::derived_from<T, object>
|
||||
[[nodiscard]] const T* as() const noexcept {
|
||||
if (is<T>()) {
|
||||
return static_cast<const T*>(this);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
/**
|
||||
* @brief 获取类型的类型信息
|
||||
* @tparam T 类型
|
||||
* @return 类型信息的常量引用
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] const type_info& get_type_info() noexcept {
|
||||
static const type_info info{
|
||||
detail::get_type_name<T>(),
|
||||
detail::compile_time_hash(detail::get_type_name<T>()),
|
||||
nullptr
|
||||
};
|
||||
return info;
|
||||
}
|
||||
|
||||
#if MIRAI_DEBUG
|
||||
void set_debug_name(std::string name) {
|
||||
debug_name_ = std::move(name);
|
||||
}
|
||||
/**
|
||||
* @brief 获取带父类的类型信息
|
||||
* @tparam T 类型
|
||||
* @tparam Parent 父类类型
|
||||
* @return 类型信息的常量引用
|
||||
*/
|
||||
template <typename T, typename Parent>
|
||||
[[nodiscard]] const type_info& get_type_info_with_parent() noexcept {
|
||||
static const type_info info{
|
||||
detail::get_type_name<T>(),
|
||||
detail::compile_time_hash(detail::get_type_name<T>()),
|
||||
&get_type_info<Parent>()
|
||||
};
|
||||
return info;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::string& get_debug_name() const noexcept {
|
||||
return debug_name_;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ============================================================================================
|
||||
// 对象标识
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取对象的唯一标识符
|
||||
* @return 对象 ID
|
||||
*/
|
||||
[[nodiscard]] object_id id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 共享指针支持
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取指向此对象的共享指针
|
||||
* @tparam T 目标类型,默认为 object
|
||||
* @return 共享指针
|
||||
* @throws std::bad_weak_ptr 如果对象不是由共享指针管理
|
||||
*/
|
||||
template<typename T = object>
|
||||
requires std::derived_from<T, object>
|
||||
[[nodiscard]] std::shared_ptr<T> shared_from_this_as() {
|
||||
return std::dynamic_pointer_cast<T>(shared_from_this());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指向此对象的共享指针(const 版本)
|
||||
* @tparam T 目标类型,默认为 object
|
||||
* @return 常量共享指针
|
||||
* @throws std::bad_weak_ptr 如果对象不是由共享指针管理
|
||||
*/
|
||||
template<typename T = object>
|
||||
requires std::derived_from<T, object>
|
||||
[[nodiscard]] std::shared_ptr<const T> shared_from_this_as() const {
|
||||
return std::dynamic_pointer_cast<const T>(shared_from_this());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指向此对象的弱指针
|
||||
* @tparam T 目标类型,默认为 object
|
||||
* @return 弱指针
|
||||
*/
|
||||
template<typename T = object>
|
||||
requires std::derived_from<T, object>
|
||||
[[nodiscard]] std::weak_ptr<T> weak_from_this_as() noexcept {
|
||||
return std::weak_ptr<T>(shared_from_this_as<T>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指向此对象的弱指针(const 版本)
|
||||
* @tparam T 目标类型,默认为 object
|
||||
* @return 常量弱指针
|
||||
*/
|
||||
template<typename T = object>
|
||||
requires std::derived_from<T, object>
|
||||
[[nodiscard]] std::weak_ptr<const T> weak_from_this_as() const noexcept {
|
||||
return std::weak_ptr<const T>(shared_from_this_as<T>());
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 生命周期回调
|
||||
// ============================================================================================
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 默认构造函数(protected,只能通过工厂函数创建)
|
||||
*/
|
||||
object();
|
||||
|
||||
/**
|
||||
* @brief 对象创建后的回调
|
||||
*
|
||||
* 在 make_object<T>() 创建对象后调用
|
||||
* 子类可以重写此方法进行初始化
|
||||
*/
|
||||
virtual void on_created() {}
|
||||
|
||||
/**
|
||||
* @brief 对象销毁前的回调
|
||||
*
|
||||
* 在智能指针的 deleter 中调用,析构函数之前
|
||||
* 子类可以重写此方法进行清理
|
||||
*
|
||||
* @note 此时 shared_from_this() 仍然可用
|
||||
*/
|
||||
virtual void on_destroying() {}
|
||||
|
||||
private:
|
||||
/// 对象唯一标识符
|
||||
object_id id_;
|
||||
// ================================================================================================
|
||||
// 对象基类
|
||||
// ================================================================================================
|
||||
|
||||
#if MIRAI_DEBUG
|
||||
std::string debug_name_;
|
||||
#endif
|
||||
|
||||
/// 全局对象 ID 计数器
|
||||
static std::atomic<object_id> next_id_;
|
||||
/**
|
||||
* @brief 对象基类
|
||||
*
|
||||
* 所有 MIRAI 框架对象的基类,提供:
|
||||
* - 共享指针支持(通过 enable_shared_from_this)
|
||||
* - 类型安全的指针转换
|
||||
* - 唯一对象标识符
|
||||
* - 运行时类型信息
|
||||
*
|
||||
* @note 对象必须通过 make_object<T>() 工厂函数创建
|
||||
* @note 对象禁止拷贝,但允许移动
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* class my_widget : public object {
|
||||
* public:
|
||||
* MIRAI_OBJECT_TYPE_INFO(my_widget, object)
|
||||
*
|
||||
* my_widget(std::string name) : name_(std::move(name)) {}
|
||||
*
|
||||
* protected:
|
||||
* void on_created() override {
|
||||
* // 初始化逻辑
|
||||
* }
|
||||
*
|
||||
* private:
|
||||
* std::string name_;
|
||||
* };
|
||||
*
|
||||
* auto widget = make_object<my_widget>("test");
|
||||
* auto shared = widget->shared_from_this_as<my_widget>();
|
||||
* @endcode
|
||||
*/
|
||||
class object : public std::enable_shared_from_this<object> {
|
||||
public:
|
||||
/// 共享指针类型别名
|
||||
using ptr = std::shared_ptr<object>;
|
||||
|
||||
/// 允许注册表访问
|
||||
friend class object_registry;
|
||||
};
|
||||
/// 弱指针类型别名
|
||||
using weak_ptr = std::weak_ptr<object>;
|
||||
|
||||
// ================================================================================================
|
||||
// 自定义删除器
|
||||
// ================================================================================================
|
||||
/**
|
||||
* @brief 虚析构函数
|
||||
*/
|
||||
virtual ~object();
|
||||
|
||||
/**
|
||||
* @brief 对象删除器
|
||||
*
|
||||
* 在删除对象前调用 on_destroying() 回调
|
||||
*
|
||||
* @tparam T 对象类型,必须派生自 object
|
||||
*/
|
||||
template<typename T>
|
||||
struct object_deleter {
|
||||
void operator()(T* ptr) const noexcept {
|
||||
if (ptr) {
|
||||
// 在析构前调用 on_destroying,此时 shared_from_this 仍可用
|
||||
ptr->on_destroying();
|
||||
delete ptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
// 禁止拷贝
|
||||
object(const object&) = delete;
|
||||
object& operator=(const object&) = delete;
|
||||
|
||||
// ================================================================================================
|
||||
// 类型信息宏
|
||||
// ================================================================================================
|
||||
// 禁止移动(因为只能通过智能指针管理)
|
||||
object(object&&) = delete;
|
||||
object& operator=(object&&) = delete;
|
||||
|
||||
/**
|
||||
* @def MIRAI_OBJECT_TYPE_INFO(class_name, parent_class)
|
||||
* @brief 声明对象类型信息
|
||||
*
|
||||
* 在派生自 object 的类中使用此宏来声明类型信息
|
||||
*
|
||||
* @param class_name 当前类名
|
||||
* @param parent_class 父类名
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* class my_widget : public object {
|
||||
* public:
|
||||
* MIRAI_OBJECT_TYPE_INFO(my_widget, object)
|
||||
* // ...
|
||||
* };
|
||||
* @endcode
|
||||
*/
|
||||
#define MIRAI_OBJECT_TYPE_INFO(class_name, parent_class) \
|
||||
// ============================================================================================
|
||||
// 类型信息
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取对象的类型信息
|
||||
* @return 类型信息的常量引用
|
||||
*/
|
||||
[[nodiscard]] virtual const type_info& get_type() const noexcept {
|
||||
return get_type_info<object>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取对象的类型名称
|
||||
* @return 类型名称字符串
|
||||
*/
|
||||
[[nodiscard]] std::string_view type_name() const noexcept {
|
||||
return get_type().name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查对象是否为指定类型
|
||||
* @tparam T 要检查的类型
|
||||
* @return 如果是指定类型返回 true
|
||||
*/
|
||||
template <typename T> requires std::derived_from<T, object>
|
||||
[[nodiscard]] bool is() const noexcept {
|
||||
return get_type().is_derived_from(get_type_info<T>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 安全地将对象转换为指定类型
|
||||
* @tparam T 目标类型
|
||||
* @return 如果转换成功返回指向对象的指针,否则返回 nullptr
|
||||
*/
|
||||
template <typename T> requires std::derived_from<T, object>
|
||||
[[nodiscard]] T* as() noexcept {
|
||||
if (is<T>()) {
|
||||
return static_cast<T*>(this);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 安全地将对象转换为指定类型(const 版本)
|
||||
* @tparam T 目标类型
|
||||
* @return 如果转换成功返回指向对象的常量指针,否则返回 nullptr
|
||||
*/
|
||||
template <typename T> requires std::derived_from<T, object>
|
||||
[[nodiscard]] const T* as() const noexcept {
|
||||
if (is<T>()) {
|
||||
return static_cast<const T*>(this);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if MIRAI_DEBUG
|
||||
void set_debug_name(std::string name) {
|
||||
debug_name_ = std::move(name);
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::string& get_debug_name() const noexcept {
|
||||
return debug_name_;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ============================================================================================
|
||||
// 对象标识
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取对象的唯一标识符
|
||||
* @return 对象 ID
|
||||
*/
|
||||
[[nodiscard]] object_id id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 共享指针支持
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取指向此对象的共享指针
|
||||
* @tparam T 目标类型,默认为 object
|
||||
* @return 共享指针
|
||||
* @throws std::bad_weak_ptr 如果对象不是由共享指针管理
|
||||
*/
|
||||
template <typename T = object> requires std::derived_from<T, object>
|
||||
[[nodiscard]] std::shared_ptr<T> shared_from_this_as() {
|
||||
return std::dynamic_pointer_cast<T>(shared_from_this());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指向此对象的共享指针(const 版本)
|
||||
* @tparam T 目标类型,默认为 object
|
||||
* @return 常量共享指针
|
||||
* @throws std::bad_weak_ptr 如果对象不是由共享指针管理
|
||||
*/
|
||||
template <typename T = object> requires std::derived_from<T, object>
|
||||
[[nodiscard]] std::shared_ptr<const T> shared_from_this_as() const {
|
||||
return std::dynamic_pointer_cast<const T>(shared_from_this());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指向此对象的弱指针
|
||||
* @tparam T 目标类型,默认为 object
|
||||
* @return 弱指针
|
||||
*/
|
||||
template <typename T = object> requires std::derived_from<T, object>
|
||||
[[nodiscard]] std::weak_ptr<T> weak_from_this_as() noexcept {
|
||||
return std::weak_ptr<T>(shared_from_this_as<T>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指向此对象的弱指针(const 版本)
|
||||
* @tparam T 目标类型,默认为 object
|
||||
* @return 常量弱指针
|
||||
*/
|
||||
template <typename T = object> requires std::derived_from<T, object>
|
||||
[[nodiscard]] std::weak_ptr<const T> weak_from_this_as() const noexcept {
|
||||
return std::weak_ptr<const T>(shared_from_this_as<T>());
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 生命周期回调
|
||||
// ============================================================================================
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 默认构造函数(protected,只能通过工厂函数创建)
|
||||
*/
|
||||
object();
|
||||
|
||||
/**
|
||||
* @brief 对象创建后的回调
|
||||
*
|
||||
* 在 make_object<T>() 创建对象后调用
|
||||
* 子类可以重写此方法进行初始化
|
||||
*/
|
||||
virtual void on_created() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 对象销毁前的回调
|
||||
*
|
||||
* 在智能指针的 deleter 中调用,析构函数之前
|
||||
* 子类可以重写此方法进行清理
|
||||
*
|
||||
* @note 此时 shared_from_this() 仍然可用
|
||||
*/
|
||||
virtual void on_destroying() {
|
||||
}
|
||||
|
||||
private:
|
||||
/// 对象唯一标识符
|
||||
object_id id_;
|
||||
|
||||
#if MIRAI_DEBUG
|
||||
std::string debug_name_;
|
||||
#endif
|
||||
|
||||
/// 全局对象 ID 计数器
|
||||
static std::atomic<object_id> next_id_;
|
||||
|
||||
/// 允许注册表访问
|
||||
friend class object_registry;
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// 自定义删除器
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 对象删除器
|
||||
*
|
||||
* 在删除对象前调用 on_destroying() 回调
|
||||
*
|
||||
* @tparam T 对象类型,必须派生自 object
|
||||
*/
|
||||
template <typename T>
|
||||
struct object_deleter {
|
||||
void operator()(T* ptr) const noexcept {
|
||||
if (ptr) {
|
||||
// 在析构前调用 on_destroying,此时 shared_from_this 仍可用
|
||||
ptr->on_destroying();
|
||||
delete ptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// 类型信息宏
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @def MIRAI_OBJECT_TYPE_INFO(class_name, parent_class)
|
||||
* @brief 声明对象类型信息
|
||||
*
|
||||
* 在派生自 object 的类中使用此宏来声明类型信息
|
||||
*
|
||||
* @param class_name 当前类名
|
||||
* @param parent_class 父类名
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* class my_widget : public object {
|
||||
* public:
|
||||
* MIRAI_OBJECT_TYPE_INFO(my_widget, object)
|
||||
* // ...
|
||||
* };
|
||||
* @endcode
|
||||
*/
|
||||
#define MIRAI_OBJECT_TYPE_INFO(class_name, parent_class) \
|
||||
[[nodiscard]] const mirai::type_info& get_type() const noexcept override { \
|
||||
return mirai::get_type_info_with_parent<class_name, parent_class>(); \
|
||||
} \
|
||||
@@ -469,56 +462,53 @@ struct object_deleter {
|
||||
friend class object_registry;
|
||||
|
||||
|
||||
// ================================================================================================
|
||||
// 工厂函数
|
||||
// ================================================================================================
|
||||
// ================================================================================================
|
||||
// 工厂函数
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 创建对象的工厂函数
|
||||
*
|
||||
* 使用此函数创建 object 派生类的实例
|
||||
* 创建后会自动调用 on_created() 回调
|
||||
*
|
||||
* @tparam T 要创建的对象类型,必须派生自 object
|
||||
* @tparam Args 构造函数参数类型
|
||||
* @param args 构造函数参数
|
||||
* @return 指向新创建对象的共享指针
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto widget = make_obj<my_widget>("test", 42);
|
||||
* @endcode
|
||||
*/
|
||||
/**
|
||||
* @brief 对象工厂辅助结构体
|
||||
*
|
||||
* 用于访问 object 派生类的 protected 构造函数和 on_created 方法
|
||||
*/
|
||||
struct object_factory {
|
||||
template<typename T, typename... Args>
|
||||
requires std::derived_from<T, object>
|
||||
static std::shared_ptr<T> create(Args&&... args) {
|
||||
std::shared_ptr<T> obj(new T(std::forward<Args>(args)...), object_deleter<T>{});
|
||||
obj->on_created();
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @brief 创建对象的工厂函数
|
||||
*
|
||||
* 使用此函数创建 object 派生类的实例
|
||||
* 创建后会自动调用 on_created() 回调
|
||||
*
|
||||
* @tparam T 要创建的对象类型,必须派生自 object
|
||||
* @tparam Args 构造函数参数类型
|
||||
* @param args 构造函数参数
|
||||
* @return 指向新创建对象的共享指针
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto widget = make_obj<my_widget>("test", 42);
|
||||
* @endcode
|
||||
*/
|
||||
/**
|
||||
* @brief 对象工厂辅助结构体
|
||||
*
|
||||
* 用于访问 object 派生类的 protected 构造函数和 on_created 方法
|
||||
*/
|
||||
struct object_factory {
|
||||
template <typename T, typename... Args> requires std::derived_from<T, object>
|
||||
static std::shared_ptr<T> create(Args&&... args) {
|
||||
std::shared_ptr<T> obj(new T(std::forward<Args>(args)...), object_deleter<T>{});
|
||||
obj->on_created();
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 创建对象的工厂函数
|
||||
*
|
||||
* 使用此函数创建 object 派生类的实例
|
||||
* 创建后会自动调用 on_created() 回调
|
||||
*
|
||||
* @tparam T 要创建的对象类型,必须派生自 object
|
||||
* @tparam Args 构造函数参数类型
|
||||
* @param args 传递给构造函数的参数
|
||||
* @return 指向新创建对象的 shared_ptr
|
||||
*/
|
||||
template<typename T, typename... Args>
|
||||
requires std::derived_from<T, object>
|
||||
[[nodiscard]] std::shared_ptr<T> make_obj(Args&&... args) {
|
||||
return object_factory::create<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
} // namespace mirai
|
||||
/**
|
||||
* @brief 创建对象的工厂函数
|
||||
*
|
||||
* 使用此函数创建 object 派生类的实例
|
||||
* 创建后会自动调用 on_created() 回调
|
||||
*
|
||||
* @tparam T 要创建的对象类型,必须派生自 object
|
||||
* @tparam Args 构造函数参数类型
|
||||
* @param args 传递给构造函数的参数
|
||||
* @return 指向新创建对象的 shared_ptr
|
||||
*/
|
||||
template <typename T, typename... Args> requires std::derived_from<T, object>
|
||||
[[nodiscard]] std::shared_ptr<T> make_obj(Args&&... args) {
|
||||
return object_factory::create<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
} // namespace mirai
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -245,35 +245,6 @@ std::string shader_inspector::get_spirv_disassembly() const {
|
||||
return "; SPIR-V disassembly would be displayed here";
|
||||
}
|
||||
|
||||
bool shader_inspector::hot_reload() {
|
||||
std::unique_lock lock(mutex_);
|
||||
|
||||
if (!shader_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
|
||||
// 实际实现应该:
|
||||
// 1. 重新编译着色器源文件
|
||||
// 2. 创建新的着色器模块
|
||||
// 3. 替换现有模块
|
||||
// 4. 更新管线
|
||||
|
||||
// 触发回调
|
||||
if (on_hot_reload_) {
|
||||
lock.unlock();
|
||||
on_hot_reload_(shader_, success);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void shader_inspector::set_on_hot_reload(hot_reload_callback callback) {
|
||||
std::unique_lock lock(mutex_);
|
||||
on_hot_reload_ = std::move(callback);
|
||||
}
|
||||
|
||||
void shader_inspector::set_on_uniform_changed(uniform_changed_callback callback) {
|
||||
std::unique_lock lock(mutex_);
|
||||
on_uniform_changed_ = std::move(callback);
|
||||
|
||||
@@ -187,39 +187,32 @@ struct shader_reflection_info {
|
||||
|
||||
/**
|
||||
* @brief 着色器检视器
|
||||
*
|
||||
*
|
||||
* @details
|
||||
* 提供着色器调试功能,包括:
|
||||
* - 查看着色器 Uniform
|
||||
* - 修改 Uniform 值
|
||||
* - 查看反射信息
|
||||
* - 着色器热重载
|
||||
*
|
||||
*
|
||||
* 使用示例:
|
||||
* @code
|
||||
* shader_inspector inspector;
|
||||
*
|
||||
*
|
||||
* // 设置着色器
|
||||
* inspector.set_shader(some_shader);
|
||||
*
|
||||
*
|
||||
* // 获取 Uniforms
|
||||
* auto uniforms = inspector.get_uniforms();
|
||||
*
|
||||
*
|
||||
* // 获取 Uniform 值
|
||||
* auto value = inspector.get_uniform_value("u_color");
|
||||
*
|
||||
*
|
||||
* // 设置 Uniform 值
|
||||
* inspector.set_uniform_value("u_color", std::vector<f32>{1.0f, 0.0f, 0.0f, 1.0f});
|
||||
*
|
||||
* // 热重载
|
||||
* inspector.hot_reload();
|
||||
* @endcode
|
||||
*/
|
||||
class shader_inspector {
|
||||
public:
|
||||
/// 热重载回调类型
|
||||
using hot_reload_callback = std::function<void(shader_program*, bool success)>;
|
||||
|
||||
/// Uniform 变化回调类型
|
||||
using uniform_changed_callback = std::function<void(const std::string& name,
|
||||
const uniform_value& value)>;
|
||||
@@ -396,22 +389,6 @@ public:
|
||||
*/
|
||||
[[nodiscard]] std::string get_spirv_disassembly() const;
|
||||
|
||||
// ============================================================================================
|
||||
// 热重载
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 热重载着色器
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool hot_reload();
|
||||
|
||||
/**
|
||||
* @brief 设置热重载回调
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
void set_on_hot_reload(hot_reload_callback callback);
|
||||
|
||||
/**
|
||||
* @brief 设置 Uniform 变化回调
|
||||
* @param callback 回调函数
|
||||
@@ -506,9 +483,6 @@ private:
|
||||
/// 预设
|
||||
std::unordered_map<std::string, uniform_preset> presets_;
|
||||
|
||||
/// 热重载回调
|
||||
hot_reload_callback on_hot_reload_;
|
||||
|
||||
/// Uniform 变化回调
|
||||
uniform_changed_callback on_uniform_changed_;
|
||||
|
||||
|
||||
38
src/demo/CMakeLists.txt
Normal file
38
src/demo/CMakeLists.txt
Normal file
@@ -0,0 +1,38 @@
|
||||
# ================================================================================================
|
||||
# MIRAI Framework - Demo 模块
|
||||
# 描述: 演示应用框架
|
||||
# ================================================================================================
|
||||
|
||||
project(mirai_demo_lib)
|
||||
|
||||
# 添加子目录
|
||||
add_subdirectory(widgets)
|
||||
|
||||
simple_library(STATIC)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC
|
||||
mirai_core
|
||||
mirai_render
|
||||
mirai_input
|
||||
mirai_demo_widgets
|
||||
SDL3::SDL3
|
||||
)
|
||||
|
||||
# 添加 widgets 目录到包含路径(使用生成器表达式)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/widgets>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/mirai/demo/widgets>
|
||||
)
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR}
|
||||
EXPORT_NAME demo
|
||||
OUTPUT_NAME mirai_demo_lib
|
||||
)
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT mirai-targets
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
message(STATUS "Configured mirai_demo_lib library")
|
||||
354
src/demo/demo_app.cpp
Normal file
354
src/demo/demo_app.cpp
Normal file
@@ -0,0 +1,354 @@
|
||||
/**
|
||||
* @file demo_app.cpp
|
||||
* @brief MIRAI 框架演示应用主类实现
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*/
|
||||
|
||||
#include "demo_app.hpp"
|
||||
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
|
||||
namespace mirai::demo {
|
||||
|
||||
// ================================================================================================
|
||||
// 构造函数和析构函数
|
||||
// ================================================================================================
|
||||
|
||||
demo_app::demo_app(const demo_window_config& config)
|
||||
: config_(config)
|
||||
, state_(demo_app_state::uninitialized)
|
||||
, should_quit_(false)
|
||||
, window_(nullptr)
|
||||
, frame_count_(0)
|
||||
, current_fps_(0.0)
|
||||
, frame_time_ms_(0.0)
|
||||
, fps_frame_count_(0)
|
||||
{
|
||||
}
|
||||
|
||||
demo_app::~demo_app() {
|
||||
if (state_ != demo_app_state::uninitialized && state_ != demo_app_state::quit) {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
// 公共方法
|
||||
// ================================================================================================
|
||||
|
||||
bool demo_app::initialize() {
|
||||
MIRAI_LOG_INFO("Initializing MIRAI Demo Application...");
|
||||
|
||||
// 初始化 SDL
|
||||
if (!init_sdl()) {
|
||||
MIRAI_LOG_ERROR("Failed to initialize SDL");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建窗口
|
||||
if (!create_window()) {
|
||||
MIRAI_LOG_ERROR("Failed to create window");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化渲染器
|
||||
if (!init_renderer()) {
|
||||
MIRAI_LOG_ERROR("Failed to initialize renderer");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化输入管理器
|
||||
if (!init_input()) {
|
||||
MIRAI_LOG_ERROR("Failed to initialize input manager");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化时间点
|
||||
last_frame_time_ = std::chrono::high_resolution_clock::now();
|
||||
fps_timer_ = last_frame_time_;
|
||||
|
||||
state_ = demo_app_state::initialized;
|
||||
MIRAI_LOG_INFO("MIRAI Demo Application initialized successfully");
|
||||
MIRAI_LOG_INFO("Window size: {}x{}", config_.width, config_.height);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void demo_app::run() {
|
||||
if (state_ != demo_app_state::initialized) {
|
||||
MIRAI_LOG_ERROR("Cannot run: application not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
state_ = demo_app_state::running;
|
||||
MIRAI_LOG_INFO("Starting main loop...");
|
||||
|
||||
while (!should_quit_) {
|
||||
// 计算帧时间
|
||||
auto current_time = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<f64> delta = current_time - last_frame_time_;
|
||||
f64 delta_time = delta.count();
|
||||
last_frame_time_ = current_time;
|
||||
|
||||
frame_time_ms_ = delta_time * 1000.0;
|
||||
|
||||
// 处理事件
|
||||
handle_events();
|
||||
|
||||
if (should_quit_) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 更新逻辑
|
||||
update(delta_time);
|
||||
|
||||
// 渲染
|
||||
render();
|
||||
|
||||
// 更新帧率统计
|
||||
update_fps_stats();
|
||||
|
||||
++frame_count_;
|
||||
}
|
||||
|
||||
MIRAI_LOG_INFO("Main loop ended after {} frames", frame_count_);
|
||||
}
|
||||
|
||||
void demo_app::shutdown() {
|
||||
MIRAI_LOG_INFO("Shutting down MIRAI Demo Application...");
|
||||
|
||||
// 等待渲染器完成
|
||||
if (renderer_) {
|
||||
renderer_->wait_idle();
|
||||
renderer_->shutdown();
|
||||
renderer_.reset();
|
||||
}
|
||||
|
||||
// 清理输入管理器
|
||||
input_manager_.reset();
|
||||
|
||||
// 销毁窗口
|
||||
if (window_) {
|
||||
SDL_DestroyWindow(window_);
|
||||
window_ = nullptr;
|
||||
}
|
||||
|
||||
// 退出 SDL
|
||||
SDL_Quit();
|
||||
|
||||
state_ = demo_app_state::quit;
|
||||
MIRAI_LOG_INFO("MIRAI Demo Application shutdown complete");
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
// 私有方法 - 初始化
|
||||
// ================================================================================================
|
||||
|
||||
bool demo_app::init_sdl() {
|
||||
MIRAI_LOG_DEBUG("Initializing SDL...");
|
||||
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
|
||||
MIRAI_LOG_ERROR("SDL_Init failed: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
MIRAI_LOG_DEBUG("SDL initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool demo_app::create_window() {
|
||||
MIRAI_LOG_DEBUG("Creating window...");
|
||||
|
||||
// 设置窗口标志
|
||||
SDL_WindowFlags flags = SDL_WINDOW_VULKAN;
|
||||
|
||||
if (config_.resizable) {
|
||||
flags |= SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
|
||||
if (config_.high_dpi) {
|
||||
flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||
}
|
||||
|
||||
// 创建窗口
|
||||
window_ = SDL_CreateWindow(
|
||||
config_.title,
|
||||
static_cast<int>(config_.width),
|
||||
static_cast<int>(config_.height),
|
||||
flags
|
||||
);
|
||||
|
||||
if (!window_) {
|
||||
MIRAI_LOG_ERROR("SDL_CreateWindow failed: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
MIRAI_LOG_DEBUG("Window created successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool demo_app::init_renderer() {
|
||||
MIRAI_LOG_DEBUG("Initializing renderer...");
|
||||
|
||||
// 创建渲染器配置
|
||||
renderer_config config;
|
||||
config.app_name = "MIRAI Demo";
|
||||
config.app_version = VK_MAKE_VERSION(0, 1, 0);
|
||||
config.enable_validation = true;
|
||||
config.enable_vsync = config_.vsync;
|
||||
config.frames_in_flight = 2;
|
||||
config.viewport_width = config_.width;
|
||||
config.viewport_height = config_.height;
|
||||
|
||||
// 创建渲染器
|
||||
renderer_ = std::make_unique<renderer>(config);
|
||||
|
||||
// 初始化渲染器
|
||||
if (!renderer_->initialize(window_)) {
|
||||
MIRAI_LOG_ERROR("Failed to initialize renderer");
|
||||
return false;
|
||||
}
|
||||
|
||||
MIRAI_LOG_DEBUG("Renderer initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool demo_app::init_input() {
|
||||
MIRAI_LOG_DEBUG("Initializing input manager...");
|
||||
|
||||
input_manager_ = std::make_unique<input_manager>();
|
||||
input_manager_->set_window(window_);
|
||||
|
||||
MIRAI_LOG_DEBUG("Input manager initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
// 私有方法 - 主循环
|
||||
// ================================================================================================
|
||||
|
||||
void demo_app::handle_events() {
|
||||
SDL_Event event;
|
||||
|
||||
while (SDL_PollEvent(&event)) {
|
||||
// 让输入管理器处理事件
|
||||
if (input_manager_) {
|
||||
input_manager_->process_sdl_event(&event);
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_QUIT:
|
||||
MIRAI_LOG_INFO("Quit event received");
|
||||
should_quit_ = true;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
||||
MIRAI_LOG_INFO("Window close requested");
|
||||
should_quit_ = true;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
// ESC 键退出
|
||||
if (event.key.key == SDLK_ESCAPE) {
|
||||
MIRAI_LOG_INFO("ESC key pressed, quitting...");
|
||||
should_quit_ = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_RESIZED:
|
||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||
handle_window_event(event);
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_MINIMIZED:
|
||||
MIRAI_LOG_DEBUG("Window minimized");
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_RESTORED:
|
||||
MIRAI_LOG_DEBUG("Window restored");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新输入管理器状态
|
||||
if (input_manager_) {
|
||||
input_manager_->update(static_cast<f32>(frame_time_ms_ / 1000.0));
|
||||
}
|
||||
}
|
||||
|
||||
void demo_app::handle_window_event(const SDL_Event& event) {
|
||||
if (event.type == SDL_EVENT_WINDOW_RESIZED ||
|
||||
event.type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED) {
|
||||
|
||||
i32 width = event.window.data1;
|
||||
i32 height = event.window.data2;
|
||||
|
||||
MIRAI_LOG_DEBUG("Window resized to {}x{}", width, height);
|
||||
|
||||
// 处理窗口大小变化
|
||||
if (renderer_ && width > 0 && height > 0) {
|
||||
if (!renderer_->on_resize(static_cast<u32>(width), static_cast<u32>(height))) {
|
||||
MIRAI_LOG_WARN("Failed to handle window resize");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void demo_app::update([[maybe_unused]] f64 delta_time) {
|
||||
// 基础框架暂时不需要更新逻辑
|
||||
// 后续可以在这里添加演示场景的更新
|
||||
}
|
||||
|
||||
void demo_app::render() {
|
||||
if (!renderer_ || !renderer_->can_render()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 开始帧
|
||||
if (!renderer_->begin_frame()) {
|
||||
// 可能是窗口最小化或交换链需要重建
|
||||
return;
|
||||
}
|
||||
|
||||
// 开始渲染(使用深灰色清屏)
|
||||
clear_color clear{0.1f, 0.1f, 0.1f, 1.0f};
|
||||
renderer_->begin_rendering(clear);
|
||||
|
||||
// 这里可以添加渲染命令
|
||||
// 基础框架只显示清屏颜色
|
||||
|
||||
// 结束渲染
|
||||
renderer_->end_rendering();
|
||||
|
||||
// 结束帧
|
||||
if (!renderer_->end_frame()) {
|
||||
MIRAI_LOG_WARN("Failed to end frame");
|
||||
}
|
||||
|
||||
// 呈现
|
||||
if (!renderer_->present()) {
|
||||
MIRAI_LOG_WARN("Failed to present frame");
|
||||
}
|
||||
}
|
||||
|
||||
void demo_app::update_fps_stats() {
|
||||
++fps_frame_count_;
|
||||
|
||||
auto current_time = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<f64> elapsed = current_time - fps_timer_;
|
||||
|
||||
if (elapsed.count() >= FPS_LOG_INTERVAL) {
|
||||
current_fps_ = static_cast<f64>(fps_frame_count_) / elapsed.count();
|
||||
|
||||
MIRAI_LOG_INFO("FPS: {:.1f} | Frame Time: {:.2f}ms", current_fps_, frame_time_ms_);
|
||||
|
||||
fps_frame_count_ = 0;
|
||||
fps_timer_ = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mirai::demo
|
||||
240
src/demo/demo_app.hpp
Normal file
240
src/demo/demo_app.hpp
Normal file
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* @file demo_app.hpp
|
||||
* @brief MIRAI 框架演示应用主类
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 演示应用主类,整合窗口管理、渲染器和输入处理。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types/types.hpp"
|
||||
#include "renderer.hpp"
|
||||
#include "input_manager.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
namespace mirai::demo {
|
||||
|
||||
/**
|
||||
* @brief 演示窗口配置
|
||||
*/
|
||||
struct demo_window_config {
|
||||
/// 窗口宽度
|
||||
u32 width = 1280;
|
||||
|
||||
/// 窗口高度
|
||||
u32 height = 720;
|
||||
|
||||
/// 窗口标题
|
||||
const char* title = "MIRAI Demo";
|
||||
|
||||
/// 是否可调整大小
|
||||
bool resizable = true;
|
||||
|
||||
/// 是否启用 VSync
|
||||
bool vsync = true;
|
||||
|
||||
/// 是否支持高 DPI
|
||||
bool high_dpi = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 演示应用状态
|
||||
*/
|
||||
enum class demo_app_state : u8 {
|
||||
/// 未初始化
|
||||
uninitialized,
|
||||
/// 已初始化
|
||||
initialized,
|
||||
/// 运行中
|
||||
running,
|
||||
/// 已退出
|
||||
quit
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 演示应用主类
|
||||
*
|
||||
* 整合窗口管理、渲染器和输入处理的演示应用
|
||||
*/
|
||||
class demo_app {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param config 窗口配置
|
||||
*/
|
||||
explicit demo_app(const demo_window_config& config = {});
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~demo_app();
|
||||
|
||||
// 禁止拷贝和移动
|
||||
demo_app(const demo_app&) = delete;
|
||||
demo_app& operator=(const demo_app&) = delete;
|
||||
demo_app(demo_app&&) = delete;
|
||||
demo_app& operator=(demo_app&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 初始化应用
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool initialize();
|
||||
|
||||
/**
|
||||
* @brief 运行主循环
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* @brief 关闭应用
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 请求退出
|
||||
*/
|
||||
void request_quit() noexcept { should_quit_ = true; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否应该退出
|
||||
* @return 是否应该退出
|
||||
*/
|
||||
[[nodiscard]] bool should_quit() const noexcept { return should_quit_; }
|
||||
|
||||
/**
|
||||
* @brief 获取应用状态
|
||||
* @return 应用状态
|
||||
*/
|
||||
[[nodiscard]] demo_app_state get_state() const noexcept { return state_; }
|
||||
|
||||
/**
|
||||
* @brief 获取渲染器
|
||||
* @return 渲染器指针
|
||||
*/
|
||||
[[nodiscard]] renderer* get_renderer() noexcept { return renderer_.get(); }
|
||||
|
||||
/**
|
||||
* @brief 获取输入管理器
|
||||
* @return 输入管理器指针
|
||||
*/
|
||||
[[nodiscard]] input_manager* get_input_manager() noexcept { return input_manager_.get(); }
|
||||
|
||||
/**
|
||||
* @brief 获取窗口
|
||||
* @return SDL 窗口指针
|
||||
*/
|
||||
[[nodiscard]] SDL_Window* get_window() noexcept { return window_; }
|
||||
|
||||
/**
|
||||
* @brief 获取当前帧率
|
||||
* @return 帧率
|
||||
*/
|
||||
[[nodiscard]] f64 get_fps() const noexcept { return current_fps_; }
|
||||
|
||||
/**
|
||||
* @brief 获取帧时间(毫秒)
|
||||
* @return 帧时间
|
||||
*/
|
||||
[[nodiscard]] f64 get_frame_time_ms() const noexcept { return frame_time_ms_; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 初始化 SDL
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool init_sdl();
|
||||
|
||||
/**
|
||||
* @brief 创建窗口
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_window();
|
||||
|
||||
/**
|
||||
* @brief 初始化渲染器
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool init_renderer();
|
||||
|
||||
/**
|
||||
* @brief 初始化输入管理器
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool init_input();
|
||||
|
||||
/**
|
||||
* @brief 处理 SDL 事件
|
||||
*/
|
||||
void handle_events();
|
||||
|
||||
/**
|
||||
* @brief 处理窗口事件
|
||||
* @param event SDL 事件
|
||||
*/
|
||||
void handle_window_event(const SDL_Event& event);
|
||||
|
||||
/**
|
||||
* @brief 更新逻辑
|
||||
* @param delta_time 帧间隔时间(秒)
|
||||
*/
|
||||
void update(f64 delta_time);
|
||||
|
||||
/**
|
||||
* @brief 渲染帧
|
||||
*/
|
||||
void render();
|
||||
|
||||
/**
|
||||
* @brief 更新帧率统计
|
||||
*/
|
||||
void update_fps_stats();
|
||||
|
||||
/// 窗口配置
|
||||
demo_window_config config_;
|
||||
|
||||
/// 应用状态
|
||||
demo_app_state state_ = demo_app_state::uninitialized;
|
||||
|
||||
/// 是否应该退出
|
||||
bool should_quit_ = false;
|
||||
|
||||
/// SDL 窗口
|
||||
SDL_Window* window_ = nullptr;
|
||||
|
||||
/// 渲染器
|
||||
std::unique_ptr<renderer> renderer_;
|
||||
|
||||
/// 输入管理器
|
||||
std::unique_ptr<input_manager> input_manager_;
|
||||
|
||||
/// 帧计数
|
||||
u64 frame_count_ = 0;
|
||||
|
||||
/// 当前帧率
|
||||
f64 current_fps_ = 0.0;
|
||||
|
||||
/// 帧时间(毫秒)
|
||||
f64 frame_time_ms_ = 0.0;
|
||||
|
||||
/// 上一帧时间点
|
||||
std::chrono::high_resolution_clock::time_point last_frame_time_;
|
||||
|
||||
/// FPS 统计时间点
|
||||
std::chrono::high_resolution_clock::time_point fps_timer_;
|
||||
|
||||
/// FPS 统计帧数
|
||||
u32 fps_frame_count_ = 0;
|
||||
|
||||
/// FPS 日志输出间隔(秒)
|
||||
static constexpr f64 FPS_LOG_INTERVAL = 1.0;
|
||||
};
|
||||
|
||||
} // namespace mirai::demo
|
||||
71
src/demo/widgets/CMakeLists.txt
Normal file
71
src/demo/widgets/CMakeLists.txt
Normal file
@@ -0,0 +1,71 @@
|
||||
# ================================================================================================
|
||||
# MIRAI Framework - Demo Widgets 模块
|
||||
# 描述: 演示用的 Shader Widget 集合
|
||||
# ================================================================================================
|
||||
|
||||
project(mirai_demo_widgets)
|
||||
|
||||
# 收集源文件
|
||||
set(DEMO_WIDGETS_SOURCES
|
||||
gradient_widget.cpp
|
||||
blur_widget.cpp
|
||||
rounded_rect_widget.cpp
|
||||
particle_widget.cpp
|
||||
)
|
||||
|
||||
set(DEMO_WIDGETS_HEADERS
|
||||
gradient_widget.hpp
|
||||
blur_widget.hpp
|
||||
rounded_rect_widget.hpp
|
||||
particle_widget.hpp
|
||||
demo_widgets.hpp
|
||||
)
|
||||
|
||||
# 创建静态库
|
||||
add_library(${PROJECT_NAME} STATIC
|
||||
${DEMO_WIDGETS_SOURCES}
|
||||
${DEMO_WIDGETS_HEADERS}
|
||||
)
|
||||
|
||||
# 设置包含目录(使用生成器表达式处理安装路径)
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/src>
|
||||
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/src/core>
|
||||
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/src/render>
|
||||
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/src/resource>
|
||||
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/src/shader>
|
||||
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/src/shader/generated>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
)
|
||||
|
||||
# 链接依赖
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC
|
||||
mirai_core
|
||||
mirai_render
|
||||
mirai_resource
|
||||
mirai_shader
|
||||
)
|
||||
|
||||
# 设置目标属性
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR}
|
||||
EXPORT_NAME demo_widgets
|
||||
OUTPUT_NAME mirai_demo_widgets
|
||||
)
|
||||
|
||||
# 安装
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT mirai-targets
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
|
||||
install(FILES ${DEMO_WIDGETS_HEADERS}
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mirai/demo/widgets
|
||||
)
|
||||
|
||||
message(STATUS "Configured mirai_demo_widgets library")
|
||||
425
src/demo/widgets/blur_widget.cpp
Normal file
425
src/demo/widgets/blur_widget.cpp
Normal file
@@ -0,0 +1,425 @@
|
||||
/**
|
||||
* @file blur_widget.cpp
|
||||
* @brief MIRAI 框架模糊效果 Widget 实现
|
||||
*/
|
||||
|
||||
#include "blur_widget.hpp"
|
||||
#include "vulkan_device.hpp"
|
||||
#include "allocator.hpp"
|
||||
#include "descriptor_pool.hpp"
|
||||
#include "command_buffer.hpp"
|
||||
#include "buffer.hpp"
|
||||
#include "pipeline.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
namespace mirai {
|
||||
namespace demo {
|
||||
|
||||
// ================================================================================================
|
||||
// blur_widget 实现
|
||||
// ================================================================================================
|
||||
|
||||
blur_widget::blur_widget() {
|
||||
// 默认模糊参数
|
||||
blur_params_.sigma = 5.0f;
|
||||
blur_params_.samples = 9;
|
||||
blur_params_.texel_size[0] = 1.0f / 1280.0f;
|
||||
blur_params_.texel_size[1] = 1.0f / 720.0f;
|
||||
}
|
||||
|
||||
blur_widget::~blur_widget() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
bool blur_widget::initialize(
|
||||
std::shared_ptr<vulkan_device> device,
|
||||
std::shared_ptr<gpu_allocator> allocator,
|
||||
std::shared_ptr<descriptor_pool> pool,
|
||||
vk::Format color_format) {
|
||||
|
||||
if (initialized_) {
|
||||
MIRAI_LOG_WARN("blur_widget already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
device_ = std::move(device);
|
||||
allocator_ = std::move(allocator);
|
||||
pool_ = std::move(pool);
|
||||
color_format_ = color_format;
|
||||
|
||||
// 创建资源
|
||||
if (!create_uniform_buffers()) {
|
||||
MIRAI_LOG_ERROR("Failed to create uniform buffers for blur_widget");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!create_descriptor_sets()) {
|
||||
MIRAI_LOG_ERROR("Failed to create descriptor sets for blur_widget");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!create_pipeline()) {
|
||||
MIRAI_LOG_ERROR("Failed to create pipeline for blur_widget");
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
MIRAI_LOG_INFO("blur_widget initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
void blur_widget::destroy() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 等待设备空闲
|
||||
if (device_) {
|
||||
device_->get_device().waitIdle();
|
||||
}
|
||||
|
||||
// 销毁描述符集布局
|
||||
if (set0_layout_ && device_) {
|
||||
device_->get_device().destroyDescriptorSetLayout(set0_layout_);
|
||||
set0_layout_ = nullptr;
|
||||
}
|
||||
if (set1_layout_ && device_) {
|
||||
device_->get_device().destroyDescriptorSetLayout(set1_layout_);
|
||||
set1_layout_ = nullptr;
|
||||
}
|
||||
|
||||
// 销毁管线
|
||||
pipeline_.reset();
|
||||
pipeline_layout_.reset();
|
||||
|
||||
// 销毁缓冲区
|
||||
bounds_ubo_.reset();
|
||||
params_ubo_.reset();
|
||||
vertex_buffer_.reset();
|
||||
|
||||
// 清理引用
|
||||
device_.reset();
|
||||
allocator_.reset();
|
||||
pool_.reset();
|
||||
|
||||
initialized_ = false;
|
||||
MIRAI_LOG_INFO("blur_widget destroyed");
|
||||
}
|
||||
|
||||
void blur_widget::set_bounds(f32 x, f32 y, f32 width, f32 height) {
|
||||
bounds_.bounds[0] = x;
|
||||
bounds_.bounds[1] = y;
|
||||
bounds_.bounds[2] = width;
|
||||
bounds_.bounds[3] = height;
|
||||
bounds_dirty_ = true;
|
||||
}
|
||||
|
||||
void blur_widget::set_opacity(f32 opacity) {
|
||||
bounds_.opacity = std::clamp(opacity, 0.0f, 1.0f);
|
||||
bounds_dirty_ = true;
|
||||
}
|
||||
|
||||
void blur_widget::set_blur_params(f32 sigma, i32 samples) {
|
||||
blur_params_.sigma = std::max(0.1f, sigma);
|
||||
blur_params_.samples = std::clamp(samples, 1, 31);
|
||||
params_dirty_ = true;
|
||||
}
|
||||
|
||||
void blur_widget::set_texture_size(u32 width, u32 height) {
|
||||
blur_params_.texel_size[0] = 1.0f / static_cast<f32>(width);
|
||||
blur_params_.texel_size[1] = 1.0f / static_cast<f32>(height);
|
||||
params_dirty_ = true;
|
||||
}
|
||||
|
||||
void blur_widget::set_backdrop_texture(vk::ImageView view, vk::Sampler samp) {
|
||||
backdrop_view_ = view;
|
||||
backdrop_sampler_ = samp;
|
||||
texture_dirty_ = true;
|
||||
}
|
||||
|
||||
void blur_widget::update(command_buffer& /*cmd*/) {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新 bounds UBO
|
||||
if (bounds_dirty_ && bounds_ubo_) {
|
||||
bounds_ubo_->write(&bounds_, sizeof(bounds_), 0);
|
||||
bounds_dirty_ = false;
|
||||
}
|
||||
|
||||
// 更新 blur params UBO
|
||||
if (params_dirty_ && params_ubo_) {
|
||||
params_ubo_->write(&blur_params_, sizeof(blur_params_), 0);
|
||||
params_dirty_ = false;
|
||||
}
|
||||
|
||||
// 更新纹理描述符
|
||||
if (texture_dirty_) {
|
||||
update_texture_descriptor();
|
||||
texture_dirty_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void blur_widget::render(command_buffer& cmd) {
|
||||
if (!initialized_ || !pipeline_ || !pipeline_->is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!backdrop_view_ || !backdrop_sampler_) {
|
||||
MIRAI_LOG_WARN("blur_widget: No backdrop texture set");
|
||||
return;
|
||||
}
|
||||
|
||||
auto vk_cmd = cmd.get_vulkan_buffer();
|
||||
auto vk_pipeline = pipeline_->get_vulkan_pipeline();
|
||||
auto vk_layout = pipeline_->get_layout();
|
||||
|
||||
// 绑定管线
|
||||
vk_cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, vk_pipeline);
|
||||
|
||||
// 绑定描述符集
|
||||
std::array<vk::DescriptorSet, 2> sets = {set0_, set1_};
|
||||
vk_cmd.bindDescriptorSets(
|
||||
vk::PipelineBindPoint::eGraphics,
|
||||
vk_layout,
|
||||
0,
|
||||
static_cast<u32>(sets.size()),
|
||||
sets.data(),
|
||||
0,
|
||||
nullptr
|
||||
);
|
||||
|
||||
// 绘制全屏四边形
|
||||
vk_cmd.draw(6, 1, 0, 0);
|
||||
}
|
||||
|
||||
bool blur_widget::create_pipeline() {
|
||||
auto vk_device = device_->get_device();
|
||||
|
||||
// 获取着色器 SPIR-V
|
||||
auto vert_spirv = shaders::quad::vertex::spirv();
|
||||
auto frag_spirv = shaders::blur::fragment::spirv();
|
||||
|
||||
if (vert_spirv.empty() || frag_spirv.empty()) {
|
||||
MIRAI_LOG_ERROR("Failed to get shader SPIR-V data");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建着色器模块
|
||||
vk::ShaderModuleCreateInfo vert_info{{}, vert_spirv.size() * sizeof(u32), vert_spirv.data()};
|
||||
auto [vert_result, vert_module] = vk_device.createShaderModule(vert_info);
|
||||
if (vert_result != vk::Result::eSuccess) {
|
||||
MIRAI_LOG_ERROR("Failed to create vertex shader module");
|
||||
return false;
|
||||
}
|
||||
|
||||
vk::ShaderModuleCreateInfo frag_info{{}, frag_spirv.size() * sizeof(u32), frag_spirv.data()};
|
||||
auto [frag_result, frag_module] = vk_device.createShaderModule(frag_info);
|
||||
if (frag_result != vk::Result::eSuccess) {
|
||||
vk_device.destroyShaderModule(vert_module);
|
||||
MIRAI_LOG_ERROR("Failed to create fragment shader module");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建管线布局
|
||||
std::array<vk::DescriptorSetLayout, 2> layouts = {set0_layout_, set1_layout_};
|
||||
|
||||
pipeline_layout_config layout_config;
|
||||
layout_config.descriptor_set_layouts = {layouts.begin(), layouts.end()};
|
||||
|
||||
pipeline_layout_ = std::make_unique<pipeline_layout>(device_, layout_config);
|
||||
if (!pipeline_layout_->is_valid()) {
|
||||
vk_device.destroyShaderModule(vert_module);
|
||||
vk_device.destroyShaderModule(frag_module);
|
||||
MIRAI_LOG_ERROR("Failed to create pipeline layout");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用构建器创建管线
|
||||
graphics_pipeline_builder builder(device_);
|
||||
|
||||
pipeline_ = builder
|
||||
.set_vertex_shader(vert_module, "main")
|
||||
.set_fragment_shader(frag_module, "main")
|
||||
.set_topology(vk::PrimitiveTopology::eTriangleList)
|
||||
.set_polygon_mode(vk::PolygonMode::eFill)
|
||||
.set_cull_mode(vk::CullModeFlagBits::eNone)
|
||||
.set_front_face(vk::FrontFace::eCounterClockwise)
|
||||
.enable_depth_test(false)
|
||||
.enable_depth_write(false)
|
||||
.enable_blending(true)
|
||||
.set_blend_factors(
|
||||
vk::BlendFactor::eSrcAlpha,
|
||||
vk::BlendFactor::eOneMinusSrcAlpha,
|
||||
vk::BlendFactor::eOne,
|
||||
vk::BlendFactor::eOneMinusSrcAlpha)
|
||||
.set_color_format(color_format_)
|
||||
.add_dynamic_state(vk::DynamicState::eViewport)
|
||||
.add_dynamic_state(vk::DynamicState::eScissor)
|
||||
.set_layout(*pipeline_layout_)
|
||||
.build();
|
||||
|
||||
// 销毁着色器模块(管线创建后不再需要)
|
||||
vk_device.destroyShaderModule(vert_module);
|
||||
vk_device.destroyShaderModule(frag_module);
|
||||
|
||||
if (!pipeline_ || !pipeline_->is_valid()) {
|
||||
MIRAI_LOG_ERROR("Failed to create graphics pipeline");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool blur_widget::create_descriptor_sets() {
|
||||
auto vk_device = device_->get_device();
|
||||
|
||||
// 创建 Set 0 布局: bounds UBO (binding 0) + backdrop sampler (binding 1)
|
||||
std::array<vk::DescriptorSetLayoutBinding, 2> set0_bindings = {{
|
||||
{0, vk::DescriptorType::eUniformBuffer, 1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, nullptr},
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1,
|
||||
vk::ShaderStageFlagBits::eFragment, nullptr}
|
||||
}};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo set0_layout_info{{},
|
||||
static_cast<u32>(set0_bindings.size()), set0_bindings.data()};
|
||||
auto [set0_result, set0_layout] = vk_device.createDescriptorSetLayout(set0_layout_info);
|
||||
if (set0_result != vk::Result::eSuccess) {
|
||||
MIRAI_LOG_ERROR("Failed to create set 0 descriptor set layout");
|
||||
return false;
|
||||
}
|
||||
set0_layout_ = set0_layout;
|
||||
|
||||
// 创建 Set 1 布局: blur params UBO (binding 0)
|
||||
vk::DescriptorSetLayoutBinding set1_binding{
|
||||
0, vk::DescriptorType::eUniformBuffer, 1,
|
||||
vk::ShaderStageFlagBits::eFragment, nullptr
|
||||
};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo set1_layout_info{{}, 1, &set1_binding};
|
||||
auto [set1_result, set1_layout] = vk_device.createDescriptorSetLayout(set1_layout_info);
|
||||
if (set1_result != vk::Result::eSuccess) {
|
||||
MIRAI_LOG_ERROR("Failed to create set 1 descriptor set layout");
|
||||
return false;
|
||||
}
|
||||
set1_layout_ = set1_layout;
|
||||
|
||||
// 分配描述符集 0
|
||||
auto set0_alloc_result = pool_->allocate(set0_layout_);
|
||||
if (!set0_alloc_result) {
|
||||
MIRAI_LOG_ERROR("Failed to allocate descriptor set 0");
|
||||
return false;
|
||||
}
|
||||
set0_ = set0_alloc_result.value()->get_vulkan_set();
|
||||
|
||||
// 分配描述符集 1
|
||||
auto set1_alloc_result = pool_->allocate(set1_layout_);
|
||||
if (!set1_alloc_result) {
|
||||
MIRAI_LOG_ERROR("Failed to allocate descriptor set 1");
|
||||
return false;
|
||||
}
|
||||
set1_ = set1_alloc_result.value()->get_vulkan_set();
|
||||
|
||||
// 更新描述符集 - UBO 部分
|
||||
vk::DescriptorBufferInfo bounds_buffer_info{
|
||||
bounds_ubo_->get_vulkan_buffer(),
|
||||
0,
|
||||
sizeof(blur_widget_bounds_ubo)
|
||||
};
|
||||
|
||||
vk::DescriptorBufferInfo params_buffer_info{
|
||||
params_ubo_->get_vulkan_buffer(),
|
||||
0,
|
||||
sizeof(blur_params_ubo)
|
||||
};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 2> writes = {{
|
||||
{set0_, 0, 0, 1, vk::DescriptorType::eUniformBuffer, nullptr, &bounds_buffer_info, nullptr},
|
||||
{set1_, 0, 0, 1, vk::DescriptorType::eUniformBuffer, nullptr, ¶ms_buffer_info, nullptr}
|
||||
}};
|
||||
|
||||
vk_device.updateDescriptorSets(static_cast<u32>(writes.size()), writes.data(), 0, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool blur_widget::create_vertex_buffer() {
|
||||
// 不需要顶点缓冲区,使用 gl_VertexIndex 生成顶点
|
||||
return true;
|
||||
}
|
||||
|
||||
bool blur_widget::create_uniform_buffers() {
|
||||
// 创建 bounds Uniform Buffer
|
||||
{
|
||||
buffer_create_info info;
|
||||
info.size = sizeof(blur_widget_bounds_ubo);
|
||||
info.usage = buffer_usage::uniform;
|
||||
info.mem_usage = memory_usage::cpu_to_gpu;
|
||||
info.persistent_mapped = true;
|
||||
info.debug_name = "blur_widget_bounds_ubo";
|
||||
|
||||
bounds_ubo_ = std::make_unique<buffer>(allocator_, info);
|
||||
if (!bounds_ubo_->is_valid()) {
|
||||
MIRAI_LOG_ERROR("Failed to create bounds uniform buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
bounds_ubo_->write(&bounds_, sizeof(bounds_), 0);
|
||||
}
|
||||
|
||||
// 创建 blur params Uniform Buffer
|
||||
{
|
||||
buffer_create_info info;
|
||||
info.size = sizeof(blur_params_ubo);
|
||||
info.usage = buffer_usage::uniform;
|
||||
info.mem_usage = memory_usage::cpu_to_gpu;
|
||||
info.persistent_mapped = true;
|
||||
info.debug_name = "blur_widget_params_ubo";
|
||||
|
||||
params_ubo_ = std::make_unique<buffer>(allocator_, info);
|
||||
if (!params_ubo_->is_valid()) {
|
||||
MIRAI_LOG_ERROR("Failed to create params uniform buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
params_ubo_->write(&blur_params_, sizeof(blur_params_), 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void blur_widget::update_texture_descriptor() {
|
||||
if (!backdrop_view_ || !backdrop_sampler_ || !set0_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto vk_device = device_->get_device();
|
||||
|
||||
vk::DescriptorImageInfo image_info{
|
||||
backdrop_sampler_,
|
||||
backdrop_view_,
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal
|
||||
};
|
||||
|
||||
vk::WriteDescriptorSet write{
|
||||
set0_,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
vk::DescriptorType::eCombinedImageSampler,
|
||||
&image_info,
|
||||
nullptr,
|
||||
nullptr
|
||||
};
|
||||
|
||||
vk_device.updateDescriptorSets(1, &write, 0, nullptr);
|
||||
}
|
||||
|
||||
} // namespace demo
|
||||
} // namespace mirai
|
||||
329
src/demo/widgets/blur_widget.hpp
Normal file
329
src/demo/widgets/blur_widget.hpp
Normal file
@@ -0,0 +1,329 @@
|
||||
/**
|
||||
* @file blur_widget.hpp
|
||||
* @brief MIRAI 框架模糊效果 Widget
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 使用 Backdrop 模式实现高斯模糊效果。
|
||||
* 使用 quad.vert + blur.frag 着色器。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types/types.hpp"
|
||||
#include "vulkan_types.hpp"
|
||||
#include "object.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// 前向声明
|
||||
class vulkan_device;
|
||||
class gpu_allocator;
|
||||
class descriptor_pool;
|
||||
class command_buffer;
|
||||
class buffer;
|
||||
class graphics_pipeline;
|
||||
class pipeline_layout;
|
||||
class texture_view;
|
||||
class sampler;
|
||||
|
||||
namespace demo {
|
||||
|
||||
// ================================================================================================
|
||||
// UBO 结构
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief Widget 边界 UBO 结构(Set 0, Binding 0)
|
||||
*/
|
||||
struct alignas(16) blur_widget_bounds_ubo {
|
||||
/// 边界 (x, y, width, height) - 归一化屏幕坐标
|
||||
std::array<f32, 4> bounds = {0.0f, 0.0f, 1.0f, 1.0f};
|
||||
/// 透明度
|
||||
f32 opacity = 1.0f;
|
||||
/// 填充
|
||||
f32 _pad[3] = {0.0f, 0.0f, 0.0f};
|
||||
};
|
||||
static_assert(sizeof(blur_widget_bounds_ubo) == 32, "blur_widget_bounds_ubo size mismatch");
|
||||
|
||||
/**
|
||||
* @brief 模糊参数 UBO 结构(Set 1, Binding 0)
|
||||
*
|
||||
* 与 blur.frag 中的 BlurParams 布局匹配
|
||||
*/
|
||||
struct alignas(16) blur_params_ubo {
|
||||
/// 高斯分布标准差
|
||||
f32 sigma = 2.0f;
|
||||
/// 采样半径(实际采样数为 (2*samples+1)^2)
|
||||
i32 samples = 3;
|
||||
/// 纹理像素大小 (1.0/width, 1.0/height)
|
||||
std::array<f32, 2> texel_size = {1.0f / 1280.0f, 1.0f / 720.0f};
|
||||
};
|
||||
static_assert(sizeof(blur_params_ubo) == 16, "blur_params_ubo size mismatch");
|
||||
|
||||
// ================================================================================================
|
||||
// blur_widget 类
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 模糊效果 Widget
|
||||
*
|
||||
* 使用 Backdrop 模式实现高斯模糊效果。
|
||||
* 需要提供背景纹理进行采样。
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto widget = std::make_unique<blur_widget>();
|
||||
* widget->initialize(device, allocator, pool);
|
||||
* widget->set_bounds(0.1f, 0.1f, 0.3f, 0.3f);
|
||||
* widget->set_blur_params(3.0f, 5);
|
||||
* widget->set_backdrop_texture(backdrop_view, backdrop_sampler);
|
||||
*
|
||||
* // 在渲染循环中
|
||||
* widget->update(cmd);
|
||||
* widget->render(cmd);
|
||||
* @endcode
|
||||
*/
|
||||
class blur_widget : public object {
|
||||
public:
|
||||
MIRAI_OBJECT_TYPE_INFO(blur_widget, object)
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
blur_widget();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~blur_widget() override;
|
||||
|
||||
// 禁止拷贝和移动
|
||||
blur_widget(const blur_widget&) = delete;
|
||||
blur_widget& operator=(const blur_widget&) = delete;
|
||||
blur_widget(blur_widget&&) = delete;
|
||||
blur_widget& operator=(blur_widget&&) = delete;
|
||||
|
||||
// ============================================================================================
|
||||
// 生命周期
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 初始化 Widget
|
||||
* @param device Vulkan 设备
|
||||
* @param allocator GPU 内存分配器
|
||||
* @param pool 描述符池
|
||||
* @param color_format 颜色附件格式
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool initialize(
|
||||
std::shared_ptr<vulkan_device> device,
|
||||
std::shared_ptr<gpu_allocator> allocator,
|
||||
std::shared_ptr<descriptor_pool> pool,
|
||||
vk::Format color_format = vk::Format::eB8G8R8A8Srgb);
|
||||
|
||||
/**
|
||||
* @brief 销毁 Widget 资源
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
* @return 是否已初始化
|
||||
*/
|
||||
[[nodiscard]] bool is_initialized() const noexcept { return initialized_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 参数设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置 Widget 边界
|
||||
* @param x X 坐标(归一化 0-1)
|
||||
* @param y Y 坐标(归一化 0-1)
|
||||
* @param width 宽度(归一化 0-1)
|
||||
* @param height 高度(归一化 0-1)
|
||||
*/
|
||||
void set_bounds(f32 x, f32 y, f32 width, f32 height);
|
||||
|
||||
/**
|
||||
* @brief 设置透明度
|
||||
* @param opacity 透明度 (0-1)
|
||||
*/
|
||||
void set_opacity(f32 opacity);
|
||||
|
||||
/**
|
||||
* @brief 设置模糊参数
|
||||
* @param sigma 高斯分布标准差
|
||||
* @param samples 采样半径
|
||||
*/
|
||||
void set_blur_params(f32 sigma, i32 samples);
|
||||
|
||||
/**
|
||||
* @brief 设置纹理像素大小
|
||||
* @param width 纹理宽度
|
||||
* @param height 纹理高度
|
||||
*/
|
||||
void set_texture_size(u32 width, u32 height);
|
||||
|
||||
/**
|
||||
* @brief 设置背景纹理
|
||||
* @param view 纹理视图
|
||||
* @param samp 采样器
|
||||
*/
|
||||
void set_backdrop_texture(vk::ImageView view, vk::Sampler samp);
|
||||
|
||||
// ============================================================================================
|
||||
// 渲染
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 更新 Uniform Buffer
|
||||
* @param cmd 命令缓冲区
|
||||
*/
|
||||
void update(command_buffer& cmd);
|
||||
|
||||
/**
|
||||
* @brief 渲染 Widget
|
||||
* @param cmd 命令缓冲区
|
||||
*/
|
||||
void render(command_buffer& cmd);
|
||||
|
||||
// ============================================================================================
|
||||
// 属性访问
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取 X 坐标
|
||||
*/
|
||||
[[nodiscard]] f32 x() const noexcept { return bounds_.bounds[0]; }
|
||||
|
||||
/**
|
||||
* @brief 获取 Y 坐标
|
||||
*/
|
||||
[[nodiscard]] f32 y() const noexcept { return bounds_.bounds[1]; }
|
||||
|
||||
/**
|
||||
* @brief 获取宽度
|
||||
*/
|
||||
[[nodiscard]] f32 width() const noexcept { return bounds_.bounds[2]; }
|
||||
|
||||
/**
|
||||
* @brief 获取高度
|
||||
*/
|
||||
[[nodiscard]] f32 height() const noexcept { return bounds_.bounds[3]; }
|
||||
|
||||
/**
|
||||
* @brief 获取透明度
|
||||
*/
|
||||
[[nodiscard]] f32 opacity() const noexcept { return bounds_.opacity; }
|
||||
|
||||
/**
|
||||
* @brief 获取模糊 sigma
|
||||
*/
|
||||
[[nodiscard]] f32 sigma() const noexcept { return blur_params_.sigma; }
|
||||
|
||||
/**
|
||||
* @brief 获取采样半径
|
||||
*/
|
||||
[[nodiscard]] i32 samples() const noexcept { return blur_params_.samples; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 创建管线
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_pipeline();
|
||||
|
||||
/**
|
||||
* @brief 创建描述符集
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_descriptor_sets();
|
||||
|
||||
/**
|
||||
* @brief 创建顶点缓冲区
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_vertex_buffer();
|
||||
|
||||
/**
|
||||
* @brief 创建 Uniform Buffer
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_uniform_buffers();
|
||||
|
||||
/**
|
||||
* @brief 更新描述符集中的纹理绑定
|
||||
*/
|
||||
void update_texture_descriptor();
|
||||
|
||||
/// 是否已初始化
|
||||
bool initialized_ = false;
|
||||
|
||||
/// 是否需要更新 bounds UBO
|
||||
bool bounds_dirty_ = true;
|
||||
|
||||
/// 是否需要更新 blur params UBO
|
||||
bool params_dirty_ = true;
|
||||
|
||||
/// 是否需要更新纹理描述符
|
||||
bool texture_dirty_ = false;
|
||||
|
||||
/// Vulkan 设备
|
||||
std::shared_ptr<vulkan_device> device_;
|
||||
|
||||
/// GPU 分配器
|
||||
std::shared_ptr<gpu_allocator> allocator_;
|
||||
|
||||
/// 描述符池
|
||||
std::shared_ptr<descriptor_pool> pool_;
|
||||
|
||||
/// 颜色格式
|
||||
vk::Format color_format_ = vk::Format::eB8G8R8A8Srgb;
|
||||
|
||||
/// 管线布局
|
||||
std::unique_ptr<pipeline_layout> pipeline_layout_;
|
||||
|
||||
/// 图形管线
|
||||
std::unique_ptr<graphics_pipeline> pipeline_;
|
||||
|
||||
/// 描述符集布局 (Set 0 - 框架)
|
||||
vk::DescriptorSetLayout set0_layout_;
|
||||
|
||||
/// 描述符集布局 (Set 1 - 用户 UBO)
|
||||
vk::DescriptorSetLayout set1_layout_;
|
||||
|
||||
/// 描述符集 (Set 0)
|
||||
vk::DescriptorSet set0_;
|
||||
|
||||
/// 描述符集 (Set 1)
|
||||
vk::DescriptorSet set1_;
|
||||
|
||||
/// 顶点缓冲区
|
||||
std::unique_ptr<buffer> vertex_buffer_;
|
||||
|
||||
/// Uniform Buffer (Widget Bounds - Set 0)
|
||||
std::unique_ptr<buffer> bounds_ubo_;
|
||||
|
||||
/// Uniform Buffer (Blur Params - Set 1)
|
||||
std::unique_ptr<buffer> params_ubo_;
|
||||
|
||||
/// Widget 边界数据
|
||||
blur_widget_bounds_ubo bounds_;
|
||||
|
||||
/// 模糊参数数据
|
||||
blur_params_ubo blur_params_;
|
||||
|
||||
/// 背景纹理视图
|
||||
vk::ImageView backdrop_view_;
|
||||
|
||||
/// 背景采样器
|
||||
vk::Sampler backdrop_sampler_;
|
||||
};
|
||||
|
||||
} // namespace demo
|
||||
} // namespace mirai
|
||||
56
src/demo/widgets/demo_widgets.hpp
Normal file
56
src/demo/widgets/demo_widgets.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @file demo_widgets.hpp
|
||||
* @brief MIRAI 框架演示 Widget 统一头文件
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 包含所有演示用的 Shader Widget。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// ============================================================================
|
||||
// 演示 Widget
|
||||
// ============================================================================
|
||||
|
||||
// 渐变效果 Widget (Procedural 模式)
|
||||
// 使用 quad.vert + gradient.frag
|
||||
// 支持线性渐变,通过 Push Constant 设置颜色和角度
|
||||
#include "gradient_widget.hpp"
|
||||
|
||||
// 模糊效果 Widget (Backdrop 模式)
|
||||
// 使用 quad.vert + blur.frag
|
||||
// 支持高斯模糊,需要背景纹理采样
|
||||
#include "blur_widget.hpp"
|
||||
|
||||
// 圆角矩形 Widget (Mask 模式)
|
||||
// 使用 quad.vert + rounded_rect.frag
|
||||
// 使用 SDF 实现高质量圆角
|
||||
#include "rounded_rect_widget.hpp"
|
||||
|
||||
// 粒子系统 Widget (Custom Mesh 模式)
|
||||
// 使用自定义顶点着色器和片段着色器
|
||||
// 支持实例化渲染
|
||||
#include "particle_widget.hpp"
|
||||
|
||||
// ============================================================================
|
||||
// 便捷类型别名
|
||||
// ============================================================================
|
||||
|
||||
namespace mirai::demo {
|
||||
|
||||
/**
|
||||
* @brief Widget 类型枚举
|
||||
*/
|
||||
enum class demo_widget_type {
|
||||
/// 渐变效果
|
||||
gradient,
|
||||
/// 模糊效果
|
||||
blur,
|
||||
/// 圆角矩形
|
||||
rounded_rect,
|
||||
/// 粒子系统
|
||||
particle
|
||||
};
|
||||
|
||||
} // namespace mirai::demo
|
||||
351
src/demo/widgets/gradient_widget.cpp
Normal file
351
src/demo/widgets/gradient_widget.cpp
Normal file
@@ -0,0 +1,351 @@
|
||||
/**
|
||||
* @file gradient_widget.cpp
|
||||
* @brief MIRAI 框架渐变效果 Widget 实现
|
||||
*/
|
||||
|
||||
#include "gradient_widget.hpp"
|
||||
#include "vulkan_device.hpp"
|
||||
#include "allocator.hpp"
|
||||
#include "descriptor_pool.hpp"
|
||||
#include "command_buffer.hpp"
|
||||
#include "buffer.hpp"
|
||||
#include "pipeline.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
namespace mirai {
|
||||
namespace demo {
|
||||
|
||||
// ================================================================================================
|
||||
// gradient_widget 实现
|
||||
// ================================================================================================
|
||||
|
||||
gradient_widget::gradient_widget() {
|
||||
// 默认渐变:从蓝色到紫色
|
||||
push_constants_.color_start[0] = 0.2f;
|
||||
push_constants_.color_start[1] = 0.4f;
|
||||
push_constants_.color_start[2] = 0.8f;
|
||||
push_constants_.color_start[3] = 1.0f;
|
||||
|
||||
push_constants_.color_end[0] = 0.8f;
|
||||
push_constants_.color_end[1] = 0.2f;
|
||||
push_constants_.color_end[2] = 0.6f;
|
||||
push_constants_.color_end[3] = 1.0f;
|
||||
|
||||
push_constants_.angle = 0.0f;
|
||||
}
|
||||
|
||||
gradient_widget::~gradient_widget() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
bool gradient_widget::initialize(
|
||||
std::shared_ptr<vulkan_device> device,
|
||||
std::shared_ptr<gpu_allocator> allocator,
|
||||
std::shared_ptr<descriptor_pool> pool,
|
||||
vk::Format color_format) {
|
||||
|
||||
if (initialized_) {
|
||||
MIRAI_LOG_WARN("gradient_widget already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
device_ = std::move(device);
|
||||
allocator_ = std::move(allocator);
|
||||
pool_ = std::move(pool);
|
||||
color_format_ = color_format;
|
||||
|
||||
// 创建资源
|
||||
if (!create_uniform_buffer()) {
|
||||
MIRAI_LOG_ERROR("Failed to create uniform buffer for gradient_widget");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!create_descriptor_sets()) {
|
||||
MIRAI_LOG_ERROR("Failed to create descriptor sets for gradient_widget");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!create_pipeline()) {
|
||||
MIRAI_LOG_ERROR("Failed to create pipeline for gradient_widget");
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
MIRAI_LOG_INFO("gradient_widget initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
void gradient_widget::destroy() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 等待设备空闲
|
||||
if (device_) {
|
||||
device_->get_device().waitIdle();
|
||||
}
|
||||
|
||||
// 销毁描述符集布局
|
||||
if (descriptor_set_layout_ && device_) {
|
||||
device_->get_device().destroyDescriptorSetLayout(descriptor_set_layout_);
|
||||
descriptor_set_layout_ = nullptr;
|
||||
}
|
||||
|
||||
// 销毁管线
|
||||
pipeline_.reset();
|
||||
pipeline_layout_.reset();
|
||||
|
||||
// 销毁缓冲区
|
||||
uniform_buffer_.reset();
|
||||
vertex_buffer_.reset();
|
||||
|
||||
// 清理引用
|
||||
device_.reset();
|
||||
allocator_.reset();
|
||||
pool_.reset();
|
||||
|
||||
initialized_ = false;
|
||||
MIRAI_LOG_INFO("gradient_widget destroyed");
|
||||
}
|
||||
|
||||
void gradient_widget::set_bounds(f32 x, f32 y, f32 width, f32 height) {
|
||||
bounds_.bounds[0] = x;
|
||||
bounds_.bounds[1] = y;
|
||||
bounds_.bounds[2] = width;
|
||||
bounds_.bounds[3] = height;
|
||||
ubo_dirty_ = true;
|
||||
}
|
||||
|
||||
void gradient_widget::set_opacity(f32 opacity) {
|
||||
bounds_.opacity = std::clamp(opacity, 0.0f, 1.0f);
|
||||
ubo_dirty_ = true;
|
||||
}
|
||||
|
||||
void gradient_widget::set_colors(const std::array<f32, 4>& start, const std::array<f32, 4>& end) {
|
||||
push_constants_.color_start = start;
|
||||
push_constants_.color_end = end;
|
||||
}
|
||||
|
||||
void gradient_widget::set_angle(f32 angle_radians) {
|
||||
push_constants_.angle = angle_radians;
|
||||
}
|
||||
|
||||
void gradient_widget::set_angle_degrees(f32 angle_degrees) {
|
||||
push_constants_.angle = angle_degrees * 3.14159265358979f / 180.0f;
|
||||
}
|
||||
|
||||
void gradient_widget::update(command_buffer& /*cmd*/) {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新 UBO
|
||||
if (ubo_dirty_ && uniform_buffer_) {
|
||||
uniform_buffer_->write(&bounds_, sizeof(bounds_), 0);
|
||||
ubo_dirty_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void gradient_widget::render(command_buffer& cmd) {
|
||||
if (!initialized_ || !pipeline_ || !pipeline_->is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto vk_cmd = cmd.get_vulkan_buffer();
|
||||
auto vk_pipeline = pipeline_->get_vulkan_pipeline();
|
||||
auto vk_layout = pipeline_->get_layout();
|
||||
|
||||
// 绑定管线
|
||||
vk_cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, vk_pipeline);
|
||||
|
||||
// 绑定描述符集
|
||||
vk_cmd.bindDescriptorSets(
|
||||
vk::PipelineBindPoint::eGraphics,
|
||||
vk_layout,
|
||||
0,
|
||||
1,
|
||||
&descriptor_set_,
|
||||
0,
|
||||
nullptr
|
||||
);
|
||||
|
||||
// 推送常量
|
||||
vk_cmd.pushConstants(
|
||||
vk_layout,
|
||||
vk::ShaderStageFlagBits::eFragment,
|
||||
0,
|
||||
sizeof(push_constants_),
|
||||
&push_constants_
|
||||
);
|
||||
|
||||
// 绘制全屏四边形(使用顶点着色器生成顶点)
|
||||
vk_cmd.draw(6, 1, 0, 0);
|
||||
}
|
||||
|
||||
bool gradient_widget::create_pipeline() {
|
||||
auto vk_device = device_->get_device();
|
||||
|
||||
// 获取着色器 SPIR-V
|
||||
auto vert_spirv = shaders::quad::vertex::spirv();
|
||||
auto frag_spirv = shaders::gradient::fragment::spirv();
|
||||
|
||||
if (vert_spirv.empty() || frag_spirv.empty()) {
|
||||
MIRAI_LOG_ERROR("Failed to get shader SPIR-V data");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建着色器模块
|
||||
vk::ShaderModuleCreateInfo vert_info{{}, vert_spirv.size() * sizeof(u32), vert_spirv.data()};
|
||||
auto [vert_result, vert_module] = vk_device.createShaderModule(vert_info);
|
||||
if (vert_result != vk::Result::eSuccess) {
|
||||
MIRAI_LOG_ERROR("Failed to create vertex shader module");
|
||||
return false;
|
||||
}
|
||||
|
||||
vk::ShaderModuleCreateInfo frag_info{{}, frag_spirv.size() * sizeof(u32), frag_spirv.data()};
|
||||
auto [frag_result, frag_module] = vk_device.createShaderModule(frag_info);
|
||||
if (frag_result != vk::Result::eSuccess) {
|
||||
vk_device.destroyShaderModule(vert_module);
|
||||
MIRAI_LOG_ERROR("Failed to create fragment shader module");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建管线布局
|
||||
vk::PushConstantRange push_constant_range{
|
||||
vk::ShaderStageFlagBits::eFragment,
|
||||
0,
|
||||
sizeof(gradient_push_constants)
|
||||
};
|
||||
|
||||
pipeline_layout_config layout_config;
|
||||
layout_config.descriptor_set_layouts = {descriptor_set_layout_};
|
||||
layout_config.push_constant_ranges = {push_constant_range};
|
||||
|
||||
pipeline_layout_ = std::make_unique<pipeline_layout>(device_, layout_config);
|
||||
if (!pipeline_layout_->is_valid()) {
|
||||
vk_device.destroyShaderModule(vert_module);
|
||||
vk_device.destroyShaderModule(frag_module);
|
||||
MIRAI_LOG_ERROR("Failed to create pipeline layout");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用构建器创建管线
|
||||
graphics_pipeline_builder builder(device_);
|
||||
|
||||
pipeline_ = builder
|
||||
.set_vertex_shader(vert_module, "main")
|
||||
.set_fragment_shader(frag_module, "main")
|
||||
// 不需要顶点输入,使用 gl_VertexIndex 生成顶点
|
||||
.set_topology(vk::PrimitiveTopology::eTriangleList)
|
||||
.set_polygon_mode(vk::PolygonMode::eFill)
|
||||
.set_cull_mode(vk::CullModeFlagBits::eNone)
|
||||
.set_front_face(vk::FrontFace::eCounterClockwise)
|
||||
.enable_depth_test(false)
|
||||
.enable_depth_write(false)
|
||||
.enable_blending(true)
|
||||
.set_blend_factors(
|
||||
vk::BlendFactor::eSrcAlpha,
|
||||
vk::BlendFactor::eOneMinusSrcAlpha,
|
||||
vk::BlendFactor::eOne,
|
||||
vk::BlendFactor::eOneMinusSrcAlpha)
|
||||
.set_color_format(color_format_)
|
||||
.add_dynamic_state(vk::DynamicState::eViewport)
|
||||
.add_dynamic_state(vk::DynamicState::eScissor)
|
||||
.set_layout(*pipeline_layout_)
|
||||
.build();
|
||||
|
||||
// 销毁着色器模块(管线创建后不再需要)
|
||||
vk_device.destroyShaderModule(vert_module);
|
||||
vk_device.destroyShaderModule(frag_module);
|
||||
|
||||
if (!pipeline_ || !pipeline_->is_valid()) {
|
||||
MIRAI_LOG_ERROR("Failed to create graphics pipeline");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gradient_widget::create_descriptor_sets() {
|
||||
auto vk_device = device_->get_device();
|
||||
|
||||
// 创建描述符集布局
|
||||
vk::DescriptorSetLayoutBinding binding{
|
||||
0,
|
||||
vk::DescriptorType::eUniformBuffer,
|
||||
1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
|
||||
nullptr
|
||||
};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo layout_info{{}, 1, &binding};
|
||||
auto [layout_result, layout] = vk_device.createDescriptorSetLayout(layout_info);
|
||||
if (layout_result != vk::Result::eSuccess) {
|
||||
MIRAI_LOG_ERROR("Failed to create descriptor set layout");
|
||||
return false;
|
||||
}
|
||||
descriptor_set_layout_ = layout;
|
||||
|
||||
// 分配描述符集
|
||||
auto set_result = pool_->allocate(descriptor_set_layout_);
|
||||
if (!set_result) {
|
||||
MIRAI_LOG_ERROR("Failed to allocate descriptor set");
|
||||
return false;
|
||||
}
|
||||
|
||||
descriptor_set_ = set_result.value()->get_vulkan_set();
|
||||
|
||||
// 更新描述符集
|
||||
vk::DescriptorBufferInfo buffer_info{
|
||||
uniform_buffer_->get_vulkan_buffer(),
|
||||
0,
|
||||
sizeof(widget_bounds_ubo)
|
||||
};
|
||||
|
||||
vk::WriteDescriptorSet write{
|
||||
descriptor_set_,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
vk::DescriptorType::eUniformBuffer,
|
||||
nullptr,
|
||||
&buffer_info,
|
||||
nullptr
|
||||
};
|
||||
|
||||
vk_device.updateDescriptorSets(1, &write, 0, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gradient_widget::create_vertex_buffer() {
|
||||
// 不需要顶点缓冲区,使用 gl_VertexIndex 生成顶点
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gradient_widget::create_uniform_buffer() {
|
||||
buffer_create_info info;
|
||||
info.size = sizeof(widget_bounds_ubo);
|
||||
info.usage = buffer_usage::uniform;
|
||||
info.mem_usage = memory_usage::cpu_to_gpu;
|
||||
info.persistent_mapped = true;
|
||||
info.debug_name = "gradient_widget_uniform_buffer";
|
||||
|
||||
uniform_buffer_ = std::make_unique<buffer>(allocator_, info);
|
||||
if (!uniform_buffer_->is_valid()) {
|
||||
MIRAI_LOG_ERROR("Failed to create uniform buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化 UBO 数据
|
||||
uniform_buffer_->write(&bounds_, sizeof(bounds_), 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace demo
|
||||
} // namespace mirai
|
||||
301
src/demo/widgets/gradient_widget.hpp
Normal file
301
src/demo/widgets/gradient_widget.hpp
Normal file
@@ -0,0 +1,301 @@
|
||||
/**
|
||||
* @file gradient_widget.hpp
|
||||
* @brief MIRAI 框架渐变效果 Widget
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 使用 Procedural 模式实现线性渐变效果。
|
||||
* 使用 quad.vert + gradient.frag 着色器。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types/types.hpp"
|
||||
#include "vulkan_types.hpp"
|
||||
#include "object.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// 前向声明
|
||||
class vulkan_device;
|
||||
class gpu_allocator;
|
||||
class descriptor_pool;
|
||||
class command_buffer;
|
||||
class buffer;
|
||||
class graphics_pipeline;
|
||||
class pipeline_layout;
|
||||
|
||||
namespace demo {
|
||||
|
||||
// ================================================================================================
|
||||
// Push Constant 结构
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 渐变效果 Push Constant 结构
|
||||
*
|
||||
* 与 gradient.frag 中的 PushConstants 布局匹配
|
||||
*/
|
||||
struct alignas(16) gradient_push_constants {
|
||||
/// 起始颜色 (RGBA)
|
||||
std::array<f32, 4> color_start = {1.0f, 0.0f, 0.0f, 1.0f};
|
||||
/// 结束颜色 (RGBA)
|
||||
std::array<f32, 4> color_end = {0.0f, 0.0f, 1.0f, 1.0f};
|
||||
/// 渐变角度(弧度)
|
||||
f32 angle = 0.0f;
|
||||
/// 填充对齐
|
||||
f32 _pad[3] = {0.0f, 0.0f, 0.0f};
|
||||
};
|
||||
static_assert(sizeof(gradient_push_constants) == 48, "gradient_push_constants size mismatch");
|
||||
|
||||
// ================================================================================================
|
||||
// Widget Bounds UBO 结构
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief Widget 边界 UBO 结构
|
||||
*
|
||||
* 与着色器中的 WidgetBounds 布局匹配
|
||||
*/
|
||||
struct alignas(16) widget_bounds_ubo {
|
||||
/// 边界 (x, y, width, height) - 归一化屏幕坐标
|
||||
std::array<f32, 4> bounds = {0.0f, 0.0f, 1.0f, 1.0f};
|
||||
/// 透明度
|
||||
f32 opacity = 1.0f;
|
||||
/// 填充
|
||||
f32 _pad[3] = {0.0f, 0.0f, 0.0f};
|
||||
};
|
||||
static_assert(sizeof(widget_bounds_ubo) == 32, "widget_bounds_ubo size mismatch");
|
||||
|
||||
// ================================================================================================
|
||||
// gradient_widget 类
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 渐变效果 Widget
|
||||
*
|
||||
* 使用 Procedural 模式实现线性渐变效果。
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto widget = std::make_unique<gradient_widget>();
|
||||
* widget->initialize(device, allocator, pool);
|
||||
* widget->set_bounds(0.1f, 0.1f, 0.3f, 0.3f);
|
||||
* widget->set_colors({1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f, 1.0f});
|
||||
* widget->set_angle(0.785f); // 45度
|
||||
*
|
||||
* // 在渲染循环中
|
||||
* widget->update(cmd);
|
||||
* widget->render(cmd);
|
||||
* @endcode
|
||||
*/
|
||||
class gradient_widget : public object {
|
||||
public:
|
||||
MIRAI_OBJECT_TYPE_INFO(gradient_widget, object)
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
gradient_widget();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~gradient_widget() override;
|
||||
|
||||
// 禁止拷贝和移动
|
||||
gradient_widget(const gradient_widget&) = delete;
|
||||
gradient_widget& operator=(const gradient_widget&) = delete;
|
||||
gradient_widget(gradient_widget&&) = delete;
|
||||
gradient_widget& operator=(gradient_widget&&) = delete;
|
||||
|
||||
// ============================================================================================
|
||||
// 生命周期
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 初始化 Widget
|
||||
* @param device Vulkan 设备
|
||||
* @param allocator GPU 内存分配器
|
||||
* @param pool 描述符池
|
||||
* @param color_format 颜色附件格式
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool initialize(
|
||||
std::shared_ptr<vulkan_device> device,
|
||||
std::shared_ptr<gpu_allocator> allocator,
|
||||
std::shared_ptr<descriptor_pool> pool,
|
||||
vk::Format color_format = vk::Format::eB8G8R8A8Srgb);
|
||||
|
||||
/**
|
||||
* @brief 销毁 Widget 资源
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
* @return 是否已初始化
|
||||
*/
|
||||
[[nodiscard]] bool is_initialized() const noexcept { return initialized_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 参数设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置 Widget 边界
|
||||
* @param x X 坐标(归一化 0-1)
|
||||
* @param y Y 坐标(归一化 0-1)
|
||||
* @param width 宽度(归一化 0-1)
|
||||
* @param height 高度(归一化 0-1)
|
||||
*/
|
||||
void set_bounds(f32 x, f32 y, f32 width, f32 height);
|
||||
|
||||
/**
|
||||
* @brief 设置透明度
|
||||
* @param opacity 透明度 (0-1)
|
||||
*/
|
||||
void set_opacity(f32 opacity);
|
||||
|
||||
/**
|
||||
* @brief 设置渐变颜色
|
||||
* @param start 起始颜色 (RGBA)
|
||||
* @param end 结束颜色 (RGBA)
|
||||
*/
|
||||
void set_colors(const std::array<f32, 4>& start, const std::array<f32, 4>& end);
|
||||
|
||||
/**
|
||||
* @brief 设置渐变角度
|
||||
* @param angle_radians 角度(弧度)
|
||||
*/
|
||||
void set_angle(f32 angle_radians);
|
||||
|
||||
/**
|
||||
* @brief 设置渐变角度(度数)
|
||||
* @param angle_degrees 角度(度)
|
||||
*/
|
||||
void set_angle_degrees(f32 angle_degrees);
|
||||
|
||||
// ============================================================================================
|
||||
// 渲染
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 更新 Uniform Buffer
|
||||
* @param cmd 命令缓冲区
|
||||
*/
|
||||
void update(command_buffer& cmd);
|
||||
|
||||
/**
|
||||
* @brief 渲染 Widget
|
||||
* @param cmd 命令缓冲区
|
||||
*/
|
||||
void render(command_buffer& cmd);
|
||||
|
||||
// ============================================================================================
|
||||
// 属性访问
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取 X 坐标
|
||||
*/
|
||||
[[nodiscard]] f32 x() const noexcept { return bounds_.bounds[0]; }
|
||||
|
||||
/**
|
||||
* @brief 获取 Y 坐标
|
||||
*/
|
||||
[[nodiscard]] f32 y() const noexcept { return bounds_.bounds[1]; }
|
||||
|
||||
/**
|
||||
* @brief 获取宽度
|
||||
*/
|
||||
[[nodiscard]] f32 width() const noexcept { return bounds_.bounds[2]; }
|
||||
|
||||
/**
|
||||
* @brief 获取高度
|
||||
*/
|
||||
[[nodiscard]] f32 height() const noexcept { return bounds_.bounds[3]; }
|
||||
|
||||
/**
|
||||
* @brief 获取透明度
|
||||
*/
|
||||
[[nodiscard]] f32 opacity() const noexcept { return bounds_.opacity; }
|
||||
|
||||
/**
|
||||
* @brief 获取渐变角度
|
||||
*/
|
||||
[[nodiscard]] f32 angle() const noexcept { return push_constants_.angle; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 创建管线
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_pipeline();
|
||||
|
||||
/**
|
||||
* @brief 创建描述符集
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_descriptor_sets();
|
||||
|
||||
/**
|
||||
* @brief 创建顶点缓冲区
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_vertex_buffer();
|
||||
|
||||
/**
|
||||
* @brief 创建 Uniform Buffer
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_uniform_buffer();
|
||||
|
||||
/// 是否已初始化
|
||||
bool initialized_ = false;
|
||||
|
||||
/// 是否需要更新 UBO
|
||||
bool ubo_dirty_ = true;
|
||||
|
||||
/// Vulkan 设备
|
||||
std::shared_ptr<vulkan_device> device_;
|
||||
|
||||
/// GPU 分配器
|
||||
std::shared_ptr<gpu_allocator> allocator_;
|
||||
|
||||
/// 描述符池
|
||||
std::shared_ptr<descriptor_pool> pool_;
|
||||
|
||||
/// 颜色格式
|
||||
vk::Format color_format_ = vk::Format::eB8G8R8A8Srgb;
|
||||
|
||||
/// 管线布局
|
||||
std::unique_ptr<pipeline_layout> pipeline_layout_;
|
||||
|
||||
/// 图形管线
|
||||
std::unique_ptr<graphics_pipeline> pipeline_;
|
||||
|
||||
/// 描述符集布局
|
||||
vk::DescriptorSetLayout descriptor_set_layout_;
|
||||
|
||||
/// 描述符集
|
||||
vk::DescriptorSet descriptor_set_;
|
||||
|
||||
/// 顶点缓冲区
|
||||
std::unique_ptr<buffer> vertex_buffer_;
|
||||
|
||||
/// Uniform Buffer (Widget Bounds)
|
||||
std::unique_ptr<buffer> uniform_buffer_;
|
||||
|
||||
/// Widget 边界数据
|
||||
widget_bounds_ubo bounds_;
|
||||
|
||||
/// Push Constant 数据
|
||||
gradient_push_constants push_constants_;
|
||||
};
|
||||
|
||||
} // namespace demo
|
||||
} // namespace mirai
|
||||
559
src/demo/widgets/particle_widget.cpp
Normal file
559
src/demo/widgets/particle_widget.cpp
Normal file
@@ -0,0 +1,559 @@
|
||||
/**
|
||||
* @file particle_widget.cpp
|
||||
* @brief MIRAI 框架粒子系统 Widget 实现
|
||||
*/
|
||||
|
||||
#include "particle_widget.hpp"
|
||||
#include "vulkan_device.hpp"
|
||||
#include "allocator.hpp"
|
||||
#include "descriptor_pool.hpp"
|
||||
#include "command_buffer.hpp"
|
||||
#include "buffer.hpp"
|
||||
#include "pipeline.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
namespace mirai {
|
||||
namespace demo {
|
||||
|
||||
// ================================================================================================
|
||||
// particle_widget 实现
|
||||
// ================================================================================================
|
||||
|
||||
particle_widget::particle_widget()
|
||||
: rng_(static_cast<u32>(std::chrono::steady_clock::now().time_since_epoch().count())) {
|
||||
// 初始化单位矩阵
|
||||
std::fill(std::begin(push_constants_.view_proj), std::end(push_constants_.view_proj), 0.0f);
|
||||
push_constants_.view_proj[0] = 1.0f;
|
||||
push_constants_.view_proj[5] = 1.0f;
|
||||
push_constants_.view_proj[10] = 1.0f;
|
||||
push_constants_.view_proj[15] = 1.0f;
|
||||
push_constants_.viewport_size[0] = 1280.0f;
|
||||
push_constants_.viewport_size[1] = 720.0f;
|
||||
}
|
||||
|
||||
particle_widget::~particle_widget() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
bool particle_widget::initialize(
|
||||
std::shared_ptr<vulkan_device> device,
|
||||
std::shared_ptr<gpu_allocator> allocator,
|
||||
std::shared_ptr<descriptor_pool> pool,
|
||||
u32 max_particles,
|
||||
vk::Format color_format) {
|
||||
|
||||
if (initialized_) {
|
||||
MIRAI_LOG_WARN("particle_widget already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
device_ = std::move(device);
|
||||
allocator_ = std::move(allocator);
|
||||
pool_ = std::move(pool);
|
||||
max_particles_ = max_particles;
|
||||
color_format_ = color_format;
|
||||
|
||||
// 预分配粒子和实例数据
|
||||
particles_.reserve(max_particles_);
|
||||
instances_.reserve(max_particles_);
|
||||
|
||||
// 创建资源
|
||||
if (!create_buffers()) {
|
||||
MIRAI_LOG_ERROR("Failed to create buffers for particle_widget");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!create_descriptor_sets()) {
|
||||
MIRAI_LOG_ERROR("Failed to create descriptor sets for particle_widget");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!create_shader_modules()) {
|
||||
MIRAI_LOG_ERROR("Failed to create shader modules for particle_widget");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!create_pipeline()) {
|
||||
MIRAI_LOG_ERROR("Failed to create pipeline for particle_widget");
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
MIRAI_LOG_INFO("particle_widget initialized successfully (max_particles={})", max_particles_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void particle_widget::destroy() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 等待设备空闲
|
||||
if (device_) {
|
||||
device_->get_device().waitIdle();
|
||||
}
|
||||
|
||||
// 销毁着色器模块
|
||||
if (vertex_shader_ && device_) {
|
||||
device_->get_device().destroyShaderModule(vertex_shader_);
|
||||
vertex_shader_ = nullptr;
|
||||
}
|
||||
if (fragment_shader_ && device_) {
|
||||
device_->get_device().destroyShaderModule(fragment_shader_);
|
||||
fragment_shader_ = nullptr;
|
||||
}
|
||||
|
||||
// 销毁描述符集布局
|
||||
if (descriptor_set_layout_ && device_) {
|
||||
device_->get_device().destroyDescriptorSetLayout(descriptor_set_layout_);
|
||||
descriptor_set_layout_ = nullptr;
|
||||
}
|
||||
|
||||
// 销毁管线
|
||||
pipeline_.reset();
|
||||
pipeline_layout_.reset();
|
||||
|
||||
// 销毁缓冲区
|
||||
instance_buffer_.reset();
|
||||
uniform_buffer_.reset();
|
||||
|
||||
// 清理粒子数据
|
||||
particles_.clear();
|
||||
instances_.clear();
|
||||
|
||||
// 清理引用
|
||||
device_.reset();
|
||||
allocator_.reset();
|
||||
pool_.reset();
|
||||
|
||||
initialized_ = false;
|
||||
MIRAI_LOG_INFO("particle_widget destroyed");
|
||||
}
|
||||
|
||||
void particle_widget::set_bounds(f32 x, f32 y, f32 width, f32 height) {
|
||||
bounds_.bounds[0] = x;
|
||||
bounds_.bounds[1] = y;
|
||||
bounds_.bounds[2] = width;
|
||||
bounds_.bounds[3] = height;
|
||||
ubo_dirty_ = true;
|
||||
}
|
||||
|
||||
void particle_widget::set_opacity(f32 opacity) {
|
||||
bounds_.opacity = std::clamp(opacity, 0.0f, 1.0f);
|
||||
ubo_dirty_ = true;
|
||||
}
|
||||
|
||||
void particle_widget::set_viewport_size(u32 width, u32 height) {
|
||||
push_constants_.viewport_size[0] = static_cast<f32>(width);
|
||||
push_constants_.viewport_size[1] = static_cast<f32>(height);
|
||||
}
|
||||
|
||||
void particle_widget::set_emitter_config(const particle_emitter_config& config) {
|
||||
emitter_config_ = config;
|
||||
}
|
||||
|
||||
void particle_widget::emit(f32 delta_time) {
|
||||
emit_accumulator_ += emitter_config_.emit_rate * delta_time;
|
||||
|
||||
while (emit_accumulator_ >= 1.0f && particles_.size() < max_particles_) {
|
||||
emit_accumulator_ -= 1.0f;
|
||||
emit_burst(1);
|
||||
}
|
||||
}
|
||||
|
||||
void particle_widget::emit_burst(u32 count) {
|
||||
for (u32 i = 0; i < count && particles_.size() < max_particles_; ++i) {
|
||||
particle p;
|
||||
|
||||
// 位置
|
||||
p.position[0] = emitter_config_.emit_position[0] +
|
||||
random_float(-1.0f, 1.0f) * emitter_config_.emit_position_variance[0];
|
||||
p.position[1] = emitter_config_.emit_position[1] +
|
||||
random_float(-1.0f, 1.0f) * emitter_config_.emit_position_variance[1];
|
||||
p.position[2] = emitter_config_.emit_position[2] +
|
||||
random_float(-1.0f, 1.0f) * emitter_config_.emit_position_variance[2];
|
||||
|
||||
// 速度
|
||||
p.velocity[0] = emitter_config_.initial_velocity[0] +
|
||||
random_float(-1.0f, 1.0f) * emitter_config_.velocity_variance[0];
|
||||
p.velocity[1] = emitter_config_.initial_velocity[1] +
|
||||
random_float(-1.0f, 1.0f) * emitter_config_.velocity_variance[1];
|
||||
p.velocity[2] = emitter_config_.initial_velocity[2] +
|
||||
random_float(-1.0f, 1.0f) * emitter_config_.velocity_variance[2];
|
||||
|
||||
// 颜色(初始为起始颜色)
|
||||
p.color = emitter_config_.start_color;
|
||||
|
||||
// 大小
|
||||
p.size = emitter_config_.start_size +
|
||||
random_float(-1.0f, 1.0f) * emitter_config_.size_variance;
|
||||
|
||||
// 生命周期
|
||||
p.life = emitter_config_.life +
|
||||
random_float(-1.0f, 1.0f) * emitter_config_.life_variance;
|
||||
p.max_life = p.life;
|
||||
|
||||
// 旋转
|
||||
p.rotation = random_float(0.0f, 6.28318f);
|
||||
p.rotation_speed = emitter_config_.rotation_speed +
|
||||
random_float(-1.0f, 1.0f) * emitter_config_.rotation_speed_variance;
|
||||
|
||||
particles_.push_back(p);
|
||||
}
|
||||
|
||||
instances_dirty_ = true;
|
||||
}
|
||||
|
||||
void particle_widget::simulate(f32 delta_time) {
|
||||
if (particles_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新所有粒子
|
||||
for (auto& p : particles_) {
|
||||
// 更新生命周期
|
||||
p.life -= delta_time;
|
||||
|
||||
// 应用重力
|
||||
p.velocity[0] += emitter_config_.gravity[0] * delta_time;
|
||||
p.velocity[1] += emitter_config_.gravity[1] * delta_time;
|
||||
p.velocity[2] += emitter_config_.gravity[2] * delta_time;
|
||||
|
||||
// 更新位置
|
||||
p.position[0] += p.velocity[0] * delta_time;
|
||||
p.position[1] += p.velocity[1] * delta_time;
|
||||
p.position[2] += p.velocity[2] * delta_time;
|
||||
|
||||
// 更新旋转
|
||||
p.rotation += p.rotation_speed * delta_time;
|
||||
|
||||
// 计算生命周期比例
|
||||
f32 life_ratio = 1.0f - (p.life / p.max_life);
|
||||
life_ratio = std::clamp(life_ratio, 0.0f, 1.0f);
|
||||
|
||||
// 插值颜色
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
p.color[j] = emitter_config_.start_color[j] * (1.0f - life_ratio) +
|
||||
emitter_config_.end_color[j] * life_ratio;
|
||||
}
|
||||
|
||||
// 插值大小
|
||||
p.size = emitter_config_.start_size * (1.0f - life_ratio) +
|
||||
emitter_config_.end_size * life_ratio;
|
||||
}
|
||||
|
||||
// 移除死亡粒子
|
||||
particles_.erase(
|
||||
std::remove_if(particles_.begin(), particles_.end(),
|
||||
[](const particle& p) { return p.life <= 0.0f; }),
|
||||
particles_.end()
|
||||
);
|
||||
|
||||
instances_dirty_ = true;
|
||||
}
|
||||
|
||||
void particle_widget::clear() {
|
||||
particles_.clear();
|
||||
instances_.clear();
|
||||
instances_dirty_ = true;
|
||||
}
|
||||
|
||||
void particle_widget::update(command_buffer& /*cmd*/) {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新 UBO
|
||||
if (ubo_dirty_ && uniform_buffer_) {
|
||||
uniform_buffer_->write(&bounds_, sizeof(bounds_), 0);
|
||||
ubo_dirty_ = false;
|
||||
}
|
||||
|
||||
// 更新实例缓冲区
|
||||
if (instances_dirty_) {
|
||||
update_instance_buffer();
|
||||
instances_dirty_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void particle_widget::render(command_buffer& cmd) {
|
||||
if (!initialized_ || !pipeline_ || !pipeline_->is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (particles_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto vk_cmd = cmd.get_vulkan_buffer();
|
||||
auto vk_pipeline = pipeline_->get_vulkan_pipeline();
|
||||
auto vk_layout = pipeline_->get_layout();
|
||||
|
||||
// 绑定管线
|
||||
vk_cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, vk_pipeline);
|
||||
|
||||
// 绑定描述符集
|
||||
vk_cmd.bindDescriptorSets(
|
||||
vk::PipelineBindPoint::eGraphics,
|
||||
vk_layout,
|
||||
0,
|
||||
1,
|
||||
&descriptor_set_,
|
||||
0,
|
||||
nullptr
|
||||
);
|
||||
|
||||
// 推送常量
|
||||
vk_cmd.pushConstants(
|
||||
vk_layout,
|
||||
vk::ShaderStageFlagBits::eVertex,
|
||||
0,
|
||||
sizeof(push_constants_),
|
||||
&push_constants_
|
||||
);
|
||||
|
||||
// 绑定实例缓冲区
|
||||
vk::Buffer buffers[] = {instance_buffer_->get_vulkan_buffer()};
|
||||
vk::DeviceSize offsets[] = {0};
|
||||
vk_cmd.bindVertexBuffers(0, 1, buffers, offsets);
|
||||
|
||||
// 绘制(每个粒子6个顶点,实例化渲染)
|
||||
u32 particle_count = static_cast<u32>(particles_.size());
|
||||
vk_cmd.draw(6, particle_count, 0, 0);
|
||||
}
|
||||
|
||||
bool particle_widget::create_pipeline() {
|
||||
if (!vertex_shader_ || !fragment_shader_) {
|
||||
MIRAI_LOG_ERROR("Shader modules not created");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建管线布局
|
||||
vk::PushConstantRange push_constant_range{
|
||||
vk::ShaderStageFlagBits::eVertex,
|
||||
0,
|
||||
sizeof(particle_push_constants)
|
||||
};
|
||||
|
||||
pipeline_layout_config layout_config;
|
||||
layout_config.descriptor_set_layouts = {descriptor_set_layout_};
|
||||
layout_config.push_constant_ranges = {push_constant_range};
|
||||
|
||||
pipeline_layout_ = std::make_unique<pipeline_layout>(device_, layout_config);
|
||||
if (!pipeline_layout_->is_valid()) {
|
||||
MIRAI_LOG_ERROR("Failed to create pipeline layout");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用构建器创建管线
|
||||
graphics_pipeline_builder builder(device_);
|
||||
|
||||
pipeline_ = builder
|
||||
.set_vertex_shader(vertex_shader_, "main")
|
||||
.set_fragment_shader(fragment_shader_, "main")
|
||||
// 实例数据输入
|
||||
.add_vertex_binding(0, sizeof(particle_instance), vk::VertexInputRate::eInstance)
|
||||
.add_vertex_attribute(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(particle_instance, position))
|
||||
.add_vertex_attribute(1, 0, vk::Format::eR32Sfloat, offsetof(particle_instance, size))
|
||||
.add_vertex_attribute(2, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(particle_instance, color))
|
||||
.add_vertex_attribute(3, 0, vk::Format::eR32Sfloat, offsetof(particle_instance, rotation))
|
||||
.set_topology(vk::PrimitiveTopology::eTriangleList)
|
||||
.set_polygon_mode(vk::PolygonMode::eFill)
|
||||
.set_cull_mode(vk::CullModeFlagBits::eNone)
|
||||
.set_front_face(vk::FrontFace::eCounterClockwise)
|
||||
.enable_depth_test(false)
|
||||
.enable_depth_write(false)
|
||||
.enable_blending(true)
|
||||
.set_blend_factors(
|
||||
vk::BlendFactor::eSrcAlpha,
|
||||
vk::BlendFactor::eOne, // 加法混合,适合发光粒子
|
||||
vk::BlendFactor::eOne,
|
||||
vk::BlendFactor::eOne)
|
||||
.set_color_format(color_format_)
|
||||
.add_dynamic_state(vk::DynamicState::eViewport)
|
||||
.add_dynamic_state(vk::DynamicState::eScissor)
|
||||
.set_layout(*pipeline_layout_)
|
||||
.build();
|
||||
|
||||
if (!pipeline_ || !pipeline_->is_valid()) {
|
||||
MIRAI_LOG_ERROR("Failed to create graphics pipeline");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool particle_widget::create_descriptor_sets() {
|
||||
auto vk_device = device_->get_device();
|
||||
|
||||
// 创建描述符集布局
|
||||
vk::DescriptorSetLayoutBinding binding{
|
||||
0,
|
||||
vk::DescriptorType::eUniformBuffer,
|
||||
1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
|
||||
nullptr
|
||||
};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo layout_info{{}, 1, &binding};
|
||||
auto [layout_result, layout] = vk_device.createDescriptorSetLayout(layout_info);
|
||||
if (layout_result != vk::Result::eSuccess) {
|
||||
MIRAI_LOG_ERROR("Failed to create descriptor set layout");
|
||||
return false;
|
||||
}
|
||||
descriptor_set_layout_ = layout;
|
||||
|
||||
// 分配描述符集
|
||||
auto set_result = pool_->allocate(descriptor_set_layout_);
|
||||
if (!set_result) {
|
||||
MIRAI_LOG_ERROR("Failed to allocate descriptor set");
|
||||
return false;
|
||||
}
|
||||
|
||||
descriptor_set_ = set_result.value()->get_vulkan_set();
|
||||
|
||||
// 更新描述符集
|
||||
vk::DescriptorBufferInfo buffer_info{
|
||||
uniform_buffer_->get_vulkan_buffer(),
|
||||
0,
|
||||
sizeof(particle_bounds_ubo)
|
||||
};
|
||||
|
||||
vk::WriteDescriptorSet write{
|
||||
descriptor_set_,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
vk::DescriptorType::eUniformBuffer,
|
||||
nullptr,
|
||||
&buffer_info,
|
||||
nullptr
|
||||
};
|
||||
|
||||
vk_device.updateDescriptorSets(1, &write, 0, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool particle_widget::create_buffers() {
|
||||
// 创建 Uniform Buffer
|
||||
{
|
||||
buffer_create_info info;
|
||||
info.size = sizeof(particle_bounds_ubo);
|
||||
info.usage = buffer_usage::uniform;
|
||||
info.mem_usage = memory_usage::cpu_to_gpu;
|
||||
info.persistent_mapped = true;
|
||||
info.debug_name = "particle_widget_uniform_buffer";
|
||||
|
||||
uniform_buffer_ = std::make_unique<buffer>(allocator_, info);
|
||||
if (!uniform_buffer_->is_valid()) {
|
||||
MIRAI_LOG_ERROR("Failed to create uniform buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
uniform_buffer_->write(&bounds_, sizeof(bounds_), 0);
|
||||
}
|
||||
|
||||
// 创建实例缓冲区
|
||||
{
|
||||
buffer_create_info info;
|
||||
info.size = sizeof(particle_instance) * max_particles_;
|
||||
info.usage = buffer_usage::vertex;
|
||||
info.mem_usage = memory_usage::cpu_to_gpu;
|
||||
info.persistent_mapped = true;
|
||||
info.debug_name = "particle_widget_instance_buffer";
|
||||
|
||||
instance_buffer_ = std::make_unique<buffer>(allocator_, info);
|
||||
if (!instance_buffer_->is_valid()) {
|
||||
MIRAI_LOG_ERROR("Failed to create instance buffer");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool particle_widget::create_shader_modules() {
|
||||
// 粒子系统使用 fullscreen 顶点着色器作为基础
|
||||
// 实际项目中应该使用专门的粒子着色器
|
||||
// 这里我们使用 fullscreen 着色器作为临时替代
|
||||
|
||||
auto vk_device = device_->get_device();
|
||||
|
||||
// 使用 fullscreen 顶点着色器
|
||||
auto vert_spirv = shaders::fullscreen::vertex::spirv();
|
||||
|
||||
// 使用 gradient 片段着色器作为临时替代
|
||||
// 因为它也输出颜色
|
||||
auto frag_spirv = shaders::gradient::fragment::spirv();
|
||||
|
||||
if (vert_spirv.empty() || frag_spirv.empty()) {
|
||||
MIRAI_LOG_ERROR("Failed to get shader SPIR-V data");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建顶点着色器模块
|
||||
vk::ShaderModuleCreateInfo vert_info{{}, vert_spirv.size() * sizeof(u32), vert_spirv.data()};
|
||||
auto [vert_result, vert_module] = vk_device.createShaderModule(vert_info);
|
||||
if (vert_result != vk::Result::eSuccess) {
|
||||
MIRAI_LOG_ERROR("Failed to create vertex shader module");
|
||||
return false;
|
||||
}
|
||||
vertex_shader_ = vert_module;
|
||||
|
||||
// 创建片段着色器模块
|
||||
vk::ShaderModuleCreateInfo frag_info{{}, frag_spirv.size() * sizeof(u32), frag_spirv.data()};
|
||||
auto [frag_result, frag_module] = vk_device.createShaderModule(frag_info);
|
||||
if (frag_result != vk::Result::eSuccess) {
|
||||
vk_device.destroyShaderModule(vertex_shader_);
|
||||
vertex_shader_ = nullptr;
|
||||
MIRAI_LOG_ERROR("Failed to create fragment shader module");
|
||||
return false;
|
||||
}
|
||||
fragment_shader_ = frag_module;
|
||||
|
||||
MIRAI_LOG_WARN("particle_widget: Using placeholder shaders. For proper particle rendering, "
|
||||
"add particle.vert and particle.frag to the shader compilation system.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
f32 particle_widget::random_float(f32 min, f32 max) {
|
||||
return min + dist_(rng_) * (max - min);
|
||||
}
|
||||
|
||||
void particle_widget::update_instance_buffer() {
|
||||
if (!instance_buffer_ || particles_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新实例数据
|
||||
instances_.resize(particles_.size());
|
||||
|
||||
for (size_t i = 0; i < particles_.size(); ++i) {
|
||||
const auto& p = particles_[i];
|
||||
auto& inst = instances_[i];
|
||||
|
||||
inst.position[0] = p.position[0];
|
||||
inst.position[1] = p.position[1];
|
||||
inst.position[2] = p.position[2];
|
||||
inst.size = p.size;
|
||||
inst.color[0] = p.color[0];
|
||||
inst.color[1] = p.color[1];
|
||||
inst.color[2] = p.color[2];
|
||||
inst.color[3] = p.color[3];
|
||||
inst.rotation = p.rotation;
|
||||
}
|
||||
|
||||
// 写入缓冲区
|
||||
instance_buffer_->write(instances_.data(),
|
||||
instances_.size() * sizeof(particle_instance), 0);
|
||||
}
|
||||
|
||||
} // namespace demo
|
||||
} // namespace mirai
|
||||
469
src/demo/widgets/particle_widget.hpp
Normal file
469
src/demo/widgets/particle_widget.hpp
Normal file
@@ -0,0 +1,469 @@
|
||||
/**
|
||||
* @file particle_widget.hpp
|
||||
* @brief MIRAI 框架粒子系统 Widget
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 使用 Custom Mesh 模式实现粒子系统。
|
||||
* 使用自定义顶点着色器和片段着色器,支持实例化渲染。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types/types.hpp"
|
||||
#include "vulkan_types.hpp"
|
||||
#include "object.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// 前向声明
|
||||
class vulkan_device;
|
||||
class gpu_allocator;
|
||||
class descriptor_pool;
|
||||
class command_buffer;
|
||||
class buffer;
|
||||
class graphics_pipeline;
|
||||
class pipeline_layout;
|
||||
|
||||
namespace demo {
|
||||
|
||||
// ================================================================================================
|
||||
// 粒子数据结构
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 单个粒子数据
|
||||
*/
|
||||
struct particle {
|
||||
/// 位置 (x, y, z)
|
||||
std::array<f32, 3> position = {0.0f, 0.0f, 0.0f};
|
||||
/// 速度 (vx, vy, vz)
|
||||
std::array<f32, 3> velocity = {0.0f, 0.0f, 0.0f};
|
||||
/// 颜色 (RGBA)
|
||||
std::array<f32, 4> color = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
/// 大小
|
||||
f32 size = 10.0f;
|
||||
/// 生命周期(秒)
|
||||
f32 life = 1.0f;
|
||||
/// 最大生命周期
|
||||
f32 max_life = 1.0f;
|
||||
/// 旋转角度(弧度)
|
||||
f32 rotation = 0.0f;
|
||||
/// 旋转速度
|
||||
f32 rotation_speed = 0.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 粒子实例数据(GPU 端)
|
||||
*
|
||||
* 用于实例化渲染的顶点属性
|
||||
*/
|
||||
struct alignas(16) particle_instance {
|
||||
/// 位置 (x, y, z)
|
||||
f32 position[3];
|
||||
/// 大小
|
||||
f32 size;
|
||||
/// 颜色 (RGBA)
|
||||
f32 color[4];
|
||||
/// 旋转角度
|
||||
f32 rotation;
|
||||
/// 填充
|
||||
f32 _pad[3];
|
||||
};
|
||||
static_assert(sizeof(particle_instance) == 48, "particle_instance size mismatch");
|
||||
|
||||
// ================================================================================================
|
||||
// Push Constant 结构
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 粒子系统 Push Constant 结构
|
||||
*/
|
||||
struct alignas(16) particle_push_constants {
|
||||
/// 视图投影矩阵
|
||||
f32 view_proj[16];
|
||||
/// 视口大小 (width, height)
|
||||
f32 viewport_size[2];
|
||||
/// 填充
|
||||
f32 _pad[2];
|
||||
};
|
||||
static_assert(sizeof(particle_push_constants) == 80, "particle_push_constants size mismatch");
|
||||
|
||||
// ================================================================================================
|
||||
// Widget Bounds UBO 结构
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief Widget 边界 UBO 结构
|
||||
*/
|
||||
struct alignas(16) particle_bounds_ubo {
|
||||
/// 边界 (x, y, width, height)
|
||||
std::array<f32, 4> bounds = {0.0f, 0.0f, 1.0f, 1.0f};
|
||||
/// 透明度
|
||||
f32 opacity = 1.0f;
|
||||
/// 填充
|
||||
f32 _pad[3] = {0.0f, 0.0f, 0.0f};
|
||||
};
|
||||
static_assert(sizeof(particle_bounds_ubo) == 32, "particle_bounds_ubo size mismatch");
|
||||
|
||||
// ================================================================================================
|
||||
// 粒子发射器配置
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 粒子发射器配置
|
||||
*/
|
||||
struct particle_emitter_config {
|
||||
/// 发射位置
|
||||
std::array<f32, 3> emit_position = {0.5f, 0.5f, 0.0f};
|
||||
/// 发射位置随机范围
|
||||
std::array<f32, 3> emit_position_variance = {0.1f, 0.1f, 0.0f};
|
||||
/// 初始速度
|
||||
std::array<f32, 3> initial_velocity = {0.0f, -0.2f, 0.0f};
|
||||
/// 速度随机范围
|
||||
std::array<f32, 3> velocity_variance = {0.1f, 0.1f, 0.0f};
|
||||
/// 重力
|
||||
std::array<f32, 3> gravity = {0.0f, 0.5f, 0.0f};
|
||||
/// 初始颜色
|
||||
std::array<f32, 4> start_color = {1.0f, 0.5f, 0.0f, 1.0f};
|
||||
/// 结束颜色
|
||||
std::array<f32, 4> end_color = {1.0f, 0.0f, 0.0f, 0.0f};
|
||||
/// 初始大小
|
||||
f32 start_size = 20.0f;
|
||||
/// 结束大小
|
||||
f32 end_size = 5.0f;
|
||||
/// 大小随机范围
|
||||
f32 size_variance = 5.0f;
|
||||
/// 生命周期
|
||||
f32 life = 2.0f;
|
||||
/// 生命周期随机范围
|
||||
f32 life_variance = 0.5f;
|
||||
/// 旋转速度
|
||||
f32 rotation_speed = 0.0f;
|
||||
/// 旋转速度随机范围
|
||||
f32 rotation_speed_variance = 1.0f;
|
||||
/// 每秒发射数量
|
||||
f32 emit_rate = 50.0f;
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// particle_widget 类
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 粒子系统 Widget
|
||||
*
|
||||
* 使用 Custom Mesh 模式实现粒子系统,支持实例化渲染。
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto widget = std::make_unique<particle_widget>();
|
||||
* widget->initialize(device, allocator, pool, 1000);
|
||||
* widget->set_bounds(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
*
|
||||
* particle_emitter_config config;
|
||||
* config.emit_position = {0.5f, 0.8f, 0.0f};
|
||||
* config.start_color = {1.0f, 0.5f, 0.0f, 1.0f};
|
||||
* widget->set_emitter_config(config);
|
||||
*
|
||||
* // 在更新循环中
|
||||
* widget->emit(delta_time);
|
||||
* widget->simulate(delta_time);
|
||||
* widget->update(cmd);
|
||||
*
|
||||
* // 在渲染循环中
|
||||
* widget->render(cmd);
|
||||
* @endcode
|
||||
*/
|
||||
class particle_widget : public object {
|
||||
public:
|
||||
MIRAI_OBJECT_TYPE_INFO(particle_widget, object)
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
particle_widget();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~particle_widget() override;
|
||||
|
||||
// 禁止拷贝和移动
|
||||
particle_widget(const particle_widget&) = delete;
|
||||
particle_widget& operator=(const particle_widget&) = delete;
|
||||
particle_widget(particle_widget&&) = delete;
|
||||
particle_widget& operator=(particle_widget&&) = delete;
|
||||
|
||||
// ============================================================================================
|
||||
// 生命周期
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 初始化 Widget
|
||||
* @param device Vulkan 设备
|
||||
* @param allocator GPU 内存分配器
|
||||
* @param pool 描述符池
|
||||
* @param max_particles 最大粒子数量
|
||||
* @param color_format 颜色附件格式
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool initialize(
|
||||
std::shared_ptr<vulkan_device> device,
|
||||
std::shared_ptr<gpu_allocator> allocator,
|
||||
std::shared_ptr<descriptor_pool> pool,
|
||||
u32 max_particles = 1000,
|
||||
vk::Format color_format = vk::Format::eB8G8R8A8Srgb);
|
||||
|
||||
/**
|
||||
* @brief 销毁 Widget 资源
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
* @return 是否已初始化
|
||||
*/
|
||||
[[nodiscard]] bool is_initialized() const noexcept { return initialized_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 参数设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置 Widget 边界
|
||||
* @param x X 坐标(归一化 0-1)
|
||||
* @param y Y 坐标(归一化 0-1)
|
||||
* @param width 宽度(归一化 0-1)
|
||||
* @param height 高度(归一化 0-1)
|
||||
*/
|
||||
void set_bounds(f32 x, f32 y, f32 width, f32 height);
|
||||
|
||||
/**
|
||||
* @brief 设置透明度
|
||||
* @param opacity 透明度 (0-1)
|
||||
*/
|
||||
void set_opacity(f32 opacity);
|
||||
|
||||
/**
|
||||
* @brief 设置视口大小
|
||||
* @param width 视口宽度
|
||||
* @param height 视口高度
|
||||
*/
|
||||
void set_viewport_size(u32 width, u32 height);
|
||||
|
||||
/**
|
||||
* @brief 设置发射器配置
|
||||
* @param config 发射器配置
|
||||
*/
|
||||
void set_emitter_config(const particle_emitter_config& config);
|
||||
|
||||
/**
|
||||
* @brief 获取发射器配置
|
||||
* @return 发射器配置引用
|
||||
*/
|
||||
[[nodiscard]] particle_emitter_config& emitter_config() noexcept { return emitter_config_; }
|
||||
[[nodiscard]] const particle_emitter_config& emitter_config() const noexcept { return emitter_config_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 粒子操作
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 发射粒子
|
||||
* @param delta_time 时间增量(秒)
|
||||
*/
|
||||
void emit(f32 delta_time);
|
||||
|
||||
/**
|
||||
* @brief 发射指定数量的粒子
|
||||
* @param count 粒子数量
|
||||
*/
|
||||
void emit_burst(u32 count);
|
||||
|
||||
/**
|
||||
* @brief 模拟粒子运动
|
||||
* @param delta_time 时间增量(秒)
|
||||
*/
|
||||
void simulate(f32 delta_time);
|
||||
|
||||
/**
|
||||
* @brief 清除所有粒子
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 获取活跃粒子数量
|
||||
* @return 活跃粒子数量
|
||||
*/
|
||||
[[nodiscard]] u32 active_particle_count() const noexcept {
|
||||
return static_cast<u32>(particles_.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取最大粒子数量
|
||||
* @return 最大粒子数量
|
||||
*/
|
||||
[[nodiscard]] u32 max_particle_count() const noexcept { return max_particles_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 渲染
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 更新 GPU 缓冲区
|
||||
* @param cmd 命令缓冲区
|
||||
*/
|
||||
void update(command_buffer& cmd);
|
||||
|
||||
/**
|
||||
* @brief 渲染粒子
|
||||
* @param cmd 命令缓冲区
|
||||
*/
|
||||
void render(command_buffer& cmd);
|
||||
|
||||
// ============================================================================================
|
||||
// 属性访问
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取 X 坐标
|
||||
*/
|
||||
[[nodiscard]] f32 x() const noexcept { return bounds_.bounds[0]; }
|
||||
|
||||
/**
|
||||
* @brief 获取 Y 坐标
|
||||
*/
|
||||
[[nodiscard]] f32 y() const noexcept { return bounds_.bounds[1]; }
|
||||
|
||||
/**
|
||||
* @brief 获取宽度
|
||||
*/
|
||||
[[nodiscard]] f32 width() const noexcept { return bounds_.bounds[2]; }
|
||||
|
||||
/**
|
||||
* @brief 获取高度
|
||||
*/
|
||||
[[nodiscard]] f32 height() const noexcept { return bounds_.bounds[3]; }
|
||||
|
||||
/**
|
||||
* @brief 获取透明度
|
||||
*/
|
||||
[[nodiscard]] f32 opacity() const noexcept { return bounds_.opacity; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 创建管线
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_pipeline();
|
||||
|
||||
/**
|
||||
* @brief 创建描述符集
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_descriptor_sets();
|
||||
|
||||
/**
|
||||
* @brief 创建缓冲区
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_buffers();
|
||||
|
||||
/**
|
||||
* @brief 创建着色器模块
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_shader_modules();
|
||||
|
||||
/**
|
||||
* @brief 生成随机浮点数
|
||||
* @param min 最小值
|
||||
* @param max 最大值
|
||||
* @return 随机值
|
||||
*/
|
||||
[[nodiscard]] f32 random_float(f32 min, f32 max);
|
||||
|
||||
/**
|
||||
* @brief 更新实例缓冲区
|
||||
*/
|
||||
void update_instance_buffer();
|
||||
|
||||
/// 是否已初始化
|
||||
bool initialized_ = false;
|
||||
|
||||
/// 是否需要更新 UBO
|
||||
bool ubo_dirty_ = true;
|
||||
|
||||
/// 是否需要更新实例缓冲区
|
||||
bool instances_dirty_ = true;
|
||||
|
||||
/// 最大粒子数量
|
||||
u32 max_particles_ = 1000;
|
||||
|
||||
/// Vulkan 设备
|
||||
std::shared_ptr<vulkan_device> device_;
|
||||
|
||||
/// GPU 分配器
|
||||
std::shared_ptr<gpu_allocator> allocator_;
|
||||
|
||||
/// 描述符池
|
||||
std::shared_ptr<descriptor_pool> pool_;
|
||||
|
||||
/// 颜色格式
|
||||
vk::Format color_format_ = vk::Format::eB8G8R8A8Srgb;
|
||||
|
||||
/// 管线布局
|
||||
std::unique_ptr<pipeline_layout> pipeline_layout_;
|
||||
|
||||
/// 图形管线
|
||||
std::unique_ptr<graphics_pipeline> pipeline_;
|
||||
|
||||
/// 描述符集布局
|
||||
vk::DescriptorSetLayout descriptor_set_layout_;
|
||||
|
||||
/// 描述符集
|
||||
vk::DescriptorSet descriptor_set_;
|
||||
|
||||
/// 顶点着色器模块
|
||||
vk::ShaderModule vertex_shader_;
|
||||
|
||||
/// 片段着色器模块
|
||||
vk::ShaderModule fragment_shader_;
|
||||
|
||||
/// 实例缓冲区
|
||||
std::unique_ptr<buffer> instance_buffer_;
|
||||
|
||||
/// Uniform Buffer (Widget Bounds)
|
||||
std::unique_ptr<buffer> uniform_buffer_;
|
||||
|
||||
/// Widget 边界数据
|
||||
particle_bounds_ubo bounds_;
|
||||
|
||||
/// Push Constant 数据
|
||||
particle_push_constants push_constants_;
|
||||
|
||||
/// 发射器配置
|
||||
particle_emitter_config emitter_config_;
|
||||
|
||||
/// 粒子列表
|
||||
std::vector<particle> particles_;
|
||||
|
||||
/// 实例数据(用于上传到 GPU)
|
||||
std::vector<particle_instance> instances_;
|
||||
|
||||
/// 发射累积器
|
||||
f32 emit_accumulator_ = 0.0f;
|
||||
|
||||
/// 随机数生成器
|
||||
std::mt19937 rng_;
|
||||
std::uniform_real_distribution<f32> dist_{0.0f, 1.0f};
|
||||
};
|
||||
|
||||
} // namespace demo
|
||||
} // namespace mirai
|
||||
350
src/demo/widgets/rounded_rect_widget.cpp
Normal file
350
src/demo/widgets/rounded_rect_widget.cpp
Normal file
@@ -0,0 +1,350 @@
|
||||
/**
|
||||
* @file rounded_rect_widget.cpp
|
||||
* @brief MIRAI 框架圆角矩形 Widget 实现
|
||||
*/
|
||||
|
||||
#include "rounded_rect_widget.hpp"
|
||||
#include "vulkan_device.hpp"
|
||||
#include "allocator.hpp"
|
||||
#include "descriptor_pool.hpp"
|
||||
#include "command_buffer.hpp"
|
||||
#include "buffer.hpp"
|
||||
#include "pipeline.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
namespace mirai {
|
||||
namespace demo {
|
||||
|
||||
// ================================================================================================
|
||||
// rounded_rect_widget 实现
|
||||
// ================================================================================================
|
||||
|
||||
rounded_rect_widget::rounded_rect_widget() {
|
||||
// 默认颜色:白色
|
||||
push_constants_.color[0] = 1.0f;
|
||||
push_constants_.color[1] = 1.0f;
|
||||
push_constants_.color[2] = 1.0f;
|
||||
push_constants_.color[3] = 1.0f;
|
||||
|
||||
// 默认圆角半径
|
||||
push_constants_.radius = 16.0f;
|
||||
}
|
||||
|
||||
rounded_rect_widget::~rounded_rect_widget() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
bool rounded_rect_widget::initialize(
|
||||
std::shared_ptr<vulkan_device> device,
|
||||
std::shared_ptr<gpu_allocator> allocator,
|
||||
std::shared_ptr<descriptor_pool> pool,
|
||||
vk::Format color_format) {
|
||||
|
||||
if (initialized_) {
|
||||
MIRAI_LOG_WARN("rounded_rect_widget already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
device_ = std::move(device);
|
||||
allocator_ = std::move(allocator);
|
||||
pool_ = std::move(pool);
|
||||
color_format_ = color_format;
|
||||
|
||||
// 创建资源
|
||||
if (!create_uniform_buffer()) {
|
||||
MIRAI_LOG_ERROR("Failed to create uniform buffer for rounded_rect_widget");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!create_descriptor_sets()) {
|
||||
MIRAI_LOG_ERROR("Failed to create descriptor sets for rounded_rect_widget");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!create_pipeline()) {
|
||||
MIRAI_LOG_ERROR("Failed to create pipeline for rounded_rect_widget");
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
MIRAI_LOG_INFO("rounded_rect_widget initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
void rounded_rect_widget::destroy() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 等待设备空闲
|
||||
if (device_) {
|
||||
device_->get_device().waitIdle();
|
||||
}
|
||||
|
||||
// 销毁描述符集布局
|
||||
if (descriptor_set_layout_ && device_) {
|
||||
device_->get_device().destroyDescriptorSetLayout(descriptor_set_layout_);
|
||||
descriptor_set_layout_ = nullptr;
|
||||
}
|
||||
|
||||
// 销毁管线
|
||||
pipeline_.reset();
|
||||
pipeline_layout_.reset();
|
||||
|
||||
// 销毁缓冲区
|
||||
uniform_buffer_.reset();
|
||||
vertex_buffer_.reset();
|
||||
|
||||
// 清理引用
|
||||
device_.reset();
|
||||
allocator_.reset();
|
||||
pool_.reset();
|
||||
|
||||
initialized_ = false;
|
||||
MIRAI_LOG_INFO("rounded_rect_widget destroyed");
|
||||
}
|
||||
|
||||
void rounded_rect_widget::set_bounds(f32 x, f32 y, f32 width, f32 height) {
|
||||
bounds_.bounds[0] = x;
|
||||
bounds_.bounds[1] = y;
|
||||
bounds_.bounds[2] = width;
|
||||
bounds_.bounds[3] = height;
|
||||
ubo_dirty_ = true;
|
||||
}
|
||||
|
||||
void rounded_rect_widget::set_bounds_pixels(f32 x, f32 y, f32 width, f32 height,
|
||||
u32 screen_width, u32 screen_height) {
|
||||
bounds_.bounds[0] = x / static_cast<f32>(screen_width);
|
||||
bounds_.bounds[1] = y / static_cast<f32>(screen_height);
|
||||
bounds_.bounds[2] = width / static_cast<f32>(screen_width);
|
||||
bounds_.bounds[3] = height / static_cast<f32>(screen_height);
|
||||
ubo_dirty_ = true;
|
||||
}
|
||||
|
||||
void rounded_rect_widget::set_opacity(f32 opacity) {
|
||||
bounds_.opacity = std::clamp(opacity, 0.0f, 1.0f);
|
||||
ubo_dirty_ = true;
|
||||
}
|
||||
|
||||
void rounded_rect_widget::set_color(const std::array<f32, 4>& color) {
|
||||
push_constants_.color = color;
|
||||
}
|
||||
|
||||
void rounded_rect_widget::set_radius(f32 radius) {
|
||||
push_constants_.radius = std::max(0.0f, radius);
|
||||
}
|
||||
|
||||
void rounded_rect_widget::update(command_buffer& /*cmd*/) {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新 UBO
|
||||
if (ubo_dirty_ && uniform_buffer_) {
|
||||
uniform_buffer_->write(&bounds_, sizeof(bounds_), 0);
|
||||
ubo_dirty_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void rounded_rect_widget::render(command_buffer& cmd) {
|
||||
if (!initialized_ || !pipeline_ || !pipeline_->is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto vk_cmd = cmd.get_vulkan_buffer();
|
||||
auto vk_pipeline = pipeline_->get_vulkan_pipeline();
|
||||
auto vk_layout = pipeline_->get_layout();
|
||||
|
||||
// 绑定管线
|
||||
vk_cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, vk_pipeline);
|
||||
|
||||
// 绑定描述符集
|
||||
vk_cmd.bindDescriptorSets(
|
||||
vk::PipelineBindPoint::eGraphics,
|
||||
vk_layout,
|
||||
0,
|
||||
1,
|
||||
&descriptor_set_,
|
||||
0,
|
||||
nullptr
|
||||
);
|
||||
|
||||
// 推送常量
|
||||
vk_cmd.pushConstants(
|
||||
vk_layout,
|
||||
vk::ShaderStageFlagBits::eFragment,
|
||||
0,
|
||||
sizeof(push_constants_),
|
||||
&push_constants_
|
||||
);
|
||||
|
||||
// 绘制全屏四边形
|
||||
vk_cmd.draw(6, 1, 0, 0);
|
||||
}
|
||||
|
||||
bool rounded_rect_widget::create_pipeline() {
|
||||
auto vk_device = device_->get_device();
|
||||
|
||||
// 获取着色器 SPIR-V
|
||||
auto vert_spirv = shaders::quad::vertex::spirv();
|
||||
auto frag_spirv = shaders::rounded_rect::fragment::spirv();
|
||||
|
||||
if (vert_spirv.empty() || frag_spirv.empty()) {
|
||||
MIRAI_LOG_ERROR("Failed to get shader SPIR-V data");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建着色器模块
|
||||
vk::ShaderModuleCreateInfo vert_info{{}, vert_spirv.size() * sizeof(u32), vert_spirv.data()};
|
||||
auto [vert_result, vert_module] = vk_device.createShaderModule(vert_info);
|
||||
if (vert_result != vk::Result::eSuccess) {
|
||||
MIRAI_LOG_ERROR("Failed to create vertex shader module");
|
||||
return false;
|
||||
}
|
||||
|
||||
vk::ShaderModuleCreateInfo frag_info{{}, frag_spirv.size() * sizeof(u32), frag_spirv.data()};
|
||||
auto [frag_result, frag_module] = vk_device.createShaderModule(frag_info);
|
||||
if (frag_result != vk::Result::eSuccess) {
|
||||
vk_device.destroyShaderModule(vert_module);
|
||||
MIRAI_LOG_ERROR("Failed to create fragment shader module");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建管线布局
|
||||
vk::PushConstantRange push_constant_range{
|
||||
vk::ShaderStageFlagBits::eFragment,
|
||||
0,
|
||||
sizeof(rounded_rect_push_constants)
|
||||
};
|
||||
|
||||
pipeline_layout_config layout_config;
|
||||
layout_config.descriptor_set_layouts = {descriptor_set_layout_};
|
||||
layout_config.push_constant_ranges = {push_constant_range};
|
||||
|
||||
pipeline_layout_ = std::make_unique<pipeline_layout>(device_, layout_config);
|
||||
if (!pipeline_layout_->is_valid()) {
|
||||
vk_device.destroyShaderModule(vert_module);
|
||||
vk_device.destroyShaderModule(frag_module);
|
||||
MIRAI_LOG_ERROR("Failed to create pipeline layout");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用构建器创建管线
|
||||
graphics_pipeline_builder builder(device_);
|
||||
|
||||
pipeline_ = builder
|
||||
.set_vertex_shader(vert_module, "main")
|
||||
.set_fragment_shader(frag_module, "main")
|
||||
.set_topology(vk::PrimitiveTopology::eTriangleList)
|
||||
.set_polygon_mode(vk::PolygonMode::eFill)
|
||||
.set_cull_mode(vk::CullModeFlagBits::eNone)
|
||||
.set_front_face(vk::FrontFace::eCounterClockwise)
|
||||
.enable_depth_test(false)
|
||||
.enable_depth_write(false)
|
||||
.enable_blending(true)
|
||||
.set_blend_factors(
|
||||
vk::BlendFactor::eSrcAlpha,
|
||||
vk::BlendFactor::eOneMinusSrcAlpha,
|
||||
vk::BlendFactor::eOne,
|
||||
vk::BlendFactor::eOneMinusSrcAlpha)
|
||||
.set_color_format(color_format_)
|
||||
.add_dynamic_state(vk::DynamicState::eViewport)
|
||||
.add_dynamic_state(vk::DynamicState::eScissor)
|
||||
.set_layout(*pipeline_layout_)
|
||||
.build();
|
||||
|
||||
// 销毁着色器模块(管线创建后不再需要)
|
||||
vk_device.destroyShaderModule(vert_module);
|
||||
vk_device.destroyShaderModule(frag_module);
|
||||
|
||||
if (!pipeline_ || !pipeline_->is_valid()) {
|
||||
MIRAI_LOG_ERROR("Failed to create graphics pipeline");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rounded_rect_widget::create_descriptor_sets() {
|
||||
auto vk_device = device_->get_device();
|
||||
|
||||
// 创建描述符集布局
|
||||
vk::DescriptorSetLayoutBinding binding{
|
||||
0,
|
||||
vk::DescriptorType::eUniformBuffer,
|
||||
1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
|
||||
nullptr
|
||||
};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo layout_info{{}, 1, &binding};
|
||||
auto [layout_result, layout] = vk_device.createDescriptorSetLayout(layout_info);
|
||||
if (layout_result != vk::Result::eSuccess) {
|
||||
MIRAI_LOG_ERROR("Failed to create descriptor set layout");
|
||||
return false;
|
||||
}
|
||||
descriptor_set_layout_ = layout;
|
||||
|
||||
// 分配描述符集
|
||||
auto set_result = pool_->allocate(descriptor_set_layout_);
|
||||
if (!set_result) {
|
||||
MIRAI_LOG_ERROR("Failed to allocate descriptor set");
|
||||
return false;
|
||||
}
|
||||
|
||||
descriptor_set_ = set_result.value()->get_vulkan_set();
|
||||
|
||||
// 更新描述符集
|
||||
vk::DescriptorBufferInfo buffer_info{
|
||||
uniform_buffer_->get_vulkan_buffer(),
|
||||
0,
|
||||
sizeof(rounded_rect_bounds_ubo)
|
||||
};
|
||||
|
||||
vk::WriteDescriptorSet write{
|
||||
descriptor_set_,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
vk::DescriptorType::eUniformBuffer,
|
||||
nullptr,
|
||||
&buffer_info,
|
||||
nullptr
|
||||
};
|
||||
|
||||
vk_device.updateDescriptorSets(1, &write, 0, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rounded_rect_widget::create_vertex_buffer() {
|
||||
// 不需要顶点缓冲区,使用 gl_VertexIndex 生成顶点
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rounded_rect_widget::create_uniform_buffer() {
|
||||
buffer_create_info info;
|
||||
info.size = sizeof(rounded_rect_bounds_ubo);
|
||||
info.usage = buffer_usage::uniform;
|
||||
info.mem_usage = memory_usage::cpu_to_gpu;
|
||||
info.persistent_mapped = true;
|
||||
info.debug_name = "rounded_rect_widget_uniform_buffer";
|
||||
|
||||
uniform_buffer_ = std::make_unique<buffer>(allocator_, info);
|
||||
if (!uniform_buffer_->is_valid()) {
|
||||
MIRAI_LOG_ERROR("Failed to create uniform buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化 UBO 数据
|
||||
uniform_buffer_->write(&bounds_, sizeof(bounds_), 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace demo
|
||||
} // namespace mirai
|
||||
307
src/demo/widgets/rounded_rect_widget.hpp
Normal file
307
src/demo/widgets/rounded_rect_widget.hpp
Normal file
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* @file rounded_rect_widget.hpp
|
||||
* @brief MIRAI 框架圆角矩形 Widget
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 使用 Mask 模式实现圆角矩形效果。
|
||||
* 使用 quad.vert + rounded_rect.frag 着色器。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types/types.hpp"
|
||||
#include "vulkan_types.hpp"
|
||||
#include "object.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// 前向声明
|
||||
class vulkan_device;
|
||||
class gpu_allocator;
|
||||
class descriptor_pool;
|
||||
class command_buffer;
|
||||
class buffer;
|
||||
class graphics_pipeline;
|
||||
class pipeline_layout;
|
||||
|
||||
namespace demo {
|
||||
|
||||
// ================================================================================================
|
||||
// Push Constant 结构
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 圆角矩形 Push Constant 结构
|
||||
*
|
||||
* 与 rounded_rect.frag 中的 PushConstants 布局匹配
|
||||
*/
|
||||
struct alignas(16) rounded_rect_push_constants {
|
||||
/// 遮罩颜色 (RGBA)
|
||||
std::array<f32, 4> color = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
/// 圆角半径(像素)
|
||||
f32 radius = 10.0f;
|
||||
/// 填充对齐
|
||||
f32 _pad[3] = {0.0f, 0.0f, 0.0f};
|
||||
};
|
||||
static_assert(sizeof(rounded_rect_push_constants) == 32, "rounded_rect_push_constants size mismatch");
|
||||
|
||||
// ================================================================================================
|
||||
// Widget Bounds UBO 结构
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief Widget 边界 UBO 结构
|
||||
*/
|
||||
struct alignas(16) rounded_rect_bounds_ubo {
|
||||
/// 边界 (x, y, width, height) - 归一化屏幕坐标
|
||||
std::array<f32, 4> bounds = {0.0f, 0.0f, 1.0f, 1.0f};
|
||||
/// 透明度
|
||||
f32 opacity = 1.0f;
|
||||
/// 填充
|
||||
f32 _pad[3] = {0.0f, 0.0f, 0.0f};
|
||||
};
|
||||
static_assert(sizeof(rounded_rect_bounds_ubo) == 32, "rounded_rect_bounds_ubo size mismatch");
|
||||
|
||||
// ================================================================================================
|
||||
// rounded_rect_widget 类
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 圆角矩形 Widget
|
||||
*
|
||||
* 使用 Mask 模式实现圆角矩形效果,使用 SDF 实现高质量抗锯齿。
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto widget = std::make_unique<rounded_rect_widget>();
|
||||
* widget->initialize(device, allocator, pool);
|
||||
* widget->set_bounds(0.1f, 0.1f, 0.3f, 0.3f);
|
||||
* widget->set_color({0.2f, 0.5f, 0.8f, 1.0f});
|
||||
* widget->set_radius(20.0f);
|
||||
*
|
||||
* // 在渲染循环中
|
||||
* widget->update(cmd);
|
||||
* widget->render(cmd);
|
||||
* @endcode
|
||||
*/
|
||||
class rounded_rect_widget : public object {
|
||||
public:
|
||||
MIRAI_OBJECT_TYPE_INFO(rounded_rect_widget, object)
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
rounded_rect_widget();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~rounded_rect_widget() override;
|
||||
|
||||
// 禁止拷贝和移动
|
||||
rounded_rect_widget(const rounded_rect_widget&) = delete;
|
||||
rounded_rect_widget& operator=(const rounded_rect_widget&) = delete;
|
||||
rounded_rect_widget(rounded_rect_widget&&) = delete;
|
||||
rounded_rect_widget& operator=(rounded_rect_widget&&) = delete;
|
||||
|
||||
// ============================================================================================
|
||||
// 生命周期
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 初始化 Widget
|
||||
* @param device Vulkan 设备
|
||||
* @param allocator GPU 内存分配器
|
||||
* @param pool 描述符池
|
||||
* @param color_format 颜色附件格式
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool initialize(
|
||||
std::shared_ptr<vulkan_device> device,
|
||||
std::shared_ptr<gpu_allocator> allocator,
|
||||
std::shared_ptr<descriptor_pool> pool,
|
||||
vk::Format color_format = vk::Format::eB8G8R8A8Srgb);
|
||||
|
||||
/**
|
||||
* @brief 销毁 Widget 资源
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
* @return 是否已初始化
|
||||
*/
|
||||
[[nodiscard]] bool is_initialized() const noexcept { return initialized_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 参数设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置 Widget 边界
|
||||
* @param x X 坐标(归一化 0-1)
|
||||
* @param y Y 坐标(归一化 0-1)
|
||||
* @param width 宽度(归一化 0-1)
|
||||
* @param height 高度(归一化 0-1)
|
||||
*/
|
||||
void set_bounds(f32 x, f32 y, f32 width, f32 height);
|
||||
|
||||
/**
|
||||
* @brief 设置 Widget 边界(像素坐标)
|
||||
* @param x X 坐标(像素)
|
||||
* @param y Y 坐标(像素)
|
||||
* @param width 宽度(像素)
|
||||
* @param height 高度(像素)
|
||||
* @param screen_width 屏幕宽度
|
||||
* @param screen_height 屏幕高度
|
||||
*/
|
||||
void set_bounds_pixels(f32 x, f32 y, f32 width, f32 height,
|
||||
u32 screen_width, u32 screen_height);
|
||||
|
||||
/**
|
||||
* @brief 设置透明度
|
||||
* @param opacity 透明度 (0-1)
|
||||
*/
|
||||
void set_opacity(f32 opacity);
|
||||
|
||||
/**
|
||||
* @brief 设置颜色
|
||||
* @param color 颜色 (RGBA)
|
||||
*/
|
||||
void set_color(const std::array<f32, 4>& color);
|
||||
|
||||
/**
|
||||
* @brief 设置圆角半径
|
||||
* @param radius 圆角半径(像素)
|
||||
*/
|
||||
void set_radius(f32 radius);
|
||||
|
||||
// ============================================================================================
|
||||
// 渲染
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 更新 Uniform Buffer
|
||||
* @param cmd 命令缓冲区
|
||||
*/
|
||||
void update(command_buffer& cmd);
|
||||
|
||||
/**
|
||||
* @brief 渲染 Widget
|
||||
* @param cmd 命令缓冲区
|
||||
*/
|
||||
void render(command_buffer& cmd);
|
||||
|
||||
// ============================================================================================
|
||||
// 属性访问
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取 X 坐标
|
||||
*/
|
||||
[[nodiscard]] f32 x() const noexcept { return bounds_.bounds[0]; }
|
||||
|
||||
/**
|
||||
* @brief 获取 Y 坐标
|
||||
*/
|
||||
[[nodiscard]] f32 y() const noexcept { return bounds_.bounds[1]; }
|
||||
|
||||
/**
|
||||
* @brief 获取宽度
|
||||
*/
|
||||
[[nodiscard]] f32 width() const noexcept { return bounds_.bounds[2]; }
|
||||
|
||||
/**
|
||||
* @brief 获取高度
|
||||
*/
|
||||
[[nodiscard]] f32 height() const noexcept { return bounds_.bounds[3]; }
|
||||
|
||||
/**
|
||||
* @brief 获取透明度
|
||||
*/
|
||||
[[nodiscard]] f32 opacity() const noexcept { return bounds_.opacity; }
|
||||
|
||||
/**
|
||||
* @brief 获取颜色
|
||||
*/
|
||||
[[nodiscard]] const std::array<f32, 4>& color() const noexcept { return push_constants_.color; }
|
||||
|
||||
/**
|
||||
* @brief 获取圆角半径
|
||||
*/
|
||||
[[nodiscard]] f32 radius() const noexcept { return push_constants_.radius; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 创建管线
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_pipeline();
|
||||
|
||||
/**
|
||||
* @brief 创建描述符集
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_descriptor_sets();
|
||||
|
||||
/**
|
||||
* @brief 创建顶点缓冲区
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_vertex_buffer();
|
||||
|
||||
/**
|
||||
* @brief 创建 Uniform Buffer
|
||||
* @return 是否成功
|
||||
*/
|
||||
[[nodiscard]] bool create_uniform_buffer();
|
||||
|
||||
/// 是否已初始化
|
||||
bool initialized_ = false;
|
||||
|
||||
/// 是否需要更新 UBO
|
||||
bool ubo_dirty_ = true;
|
||||
|
||||
/// Vulkan 设备
|
||||
std::shared_ptr<vulkan_device> device_;
|
||||
|
||||
/// GPU 分配器
|
||||
std::shared_ptr<gpu_allocator> allocator_;
|
||||
|
||||
/// 描述符池
|
||||
std::shared_ptr<descriptor_pool> pool_;
|
||||
|
||||
/// 颜色格式
|
||||
vk::Format color_format_ = vk::Format::eB8G8R8A8Srgb;
|
||||
|
||||
/// 管线布局
|
||||
std::unique_ptr<pipeline_layout> pipeline_layout_;
|
||||
|
||||
/// 图形管线
|
||||
std::unique_ptr<graphics_pipeline> pipeline_;
|
||||
|
||||
/// 描述符集布局
|
||||
vk::DescriptorSetLayout descriptor_set_layout_;
|
||||
|
||||
/// 描述符集
|
||||
vk::DescriptorSet descriptor_set_;
|
||||
|
||||
/// 顶点缓冲区
|
||||
std::unique_ptr<buffer> vertex_buffer_;
|
||||
|
||||
/// Uniform Buffer (Widget Bounds)
|
||||
std::unique_ptr<buffer> uniform_buffer_;
|
||||
|
||||
/// Widget 边界数据
|
||||
rounded_rect_bounds_ubo bounds_;
|
||||
|
||||
/// Push Constant 数据
|
||||
rounded_rect_push_constants push_constants_;
|
||||
};
|
||||
|
||||
} // namespace demo
|
||||
} // namespace mirai
|
||||
202
src/render/render_context.cpp
Normal file
202
src/render/render_context.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* @file render_context.cpp
|
||||
* @brief MIRAI 框架渲染上下文实现
|
||||
*/
|
||||
|
||||
#include "render_context.hpp"
|
||||
|
||||
namespace mirai {
|
||||
|
||||
render_context::render_context(renderer* renderer, vk::CommandBuffer cmd_buffer, render_phase phase)
|
||||
: renderer_(renderer)
|
||||
, cmd_buffer_(cmd_buffer)
|
||||
, phase_(phase) {
|
||||
}
|
||||
|
||||
render_context::~render_context() = default;
|
||||
|
||||
batch_renderer* render_context::get_batch_renderer() const {
|
||||
if (renderer_) {
|
||||
return renderer_->get_batch_renderer();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u32 render_context::get_frame_index() const {
|
||||
if (renderer_) {
|
||||
return renderer_->get_current_frame_index();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 render_context::get_image_index() const {
|
||||
if (renderer_) {
|
||||
return renderer_->get_current_image_index();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
vk::Extent2D render_context::get_swapchain_extent() const {
|
||||
if (renderer_) {
|
||||
return renderer_->get_swapchain_extent();
|
||||
}
|
||||
return vk::Extent2D{0, 0};
|
||||
}
|
||||
|
||||
vk::Format render_context::get_swapchain_format() const {
|
||||
if (renderer_) {
|
||||
return renderer_->get_swapchain_format();
|
||||
}
|
||||
return vk::Format::eUndefined;
|
||||
}
|
||||
|
||||
vk::Format render_context::get_depth_format() const {
|
||||
// 默认使用 D32Sfloat
|
||||
return vk::Format::eD32Sfloat;
|
||||
}
|
||||
|
||||
void render_context::bind_pipeline(vk::Pipeline pipeline) {
|
||||
if (cmd_buffer_) {
|
||||
cmd_buffer_.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
void render_context::bind_pipeline_layout(vk::PipelineLayout layout) {
|
||||
current_pipeline_layout_ = layout;
|
||||
}
|
||||
|
||||
void render_context::bind_descriptor_sets(vk::PipelineBindPoint bind_point,
|
||||
vk::PipelineLayout layout,
|
||||
u32 first_set,
|
||||
std::span<const vk::DescriptorSet> descriptor_sets,
|
||||
std::span<const u32> dynamic_offsets) {
|
||||
if (cmd_buffer_) {
|
||||
cmd_buffer_.bindDescriptorSets(bind_point, layout, first_set,
|
||||
static_cast<u32>(descriptor_sets.size()),
|
||||
descriptor_sets.data(),
|
||||
static_cast<u32>(dynamic_offsets.size()),
|
||||
dynamic_offsets.data());
|
||||
}
|
||||
}
|
||||
|
||||
void render_context::push_constants(vk::ShaderStageFlags stage_flags, u32 offset, std::span<const u8> data) {
|
||||
if (cmd_buffer_ && current_pipeline_layout_ && !data.empty()) {
|
||||
cmd_buffer_.pushConstants(current_pipeline_layout_, stage_flags, offset,
|
||||
static_cast<u32>(data.size()), data.data());
|
||||
}
|
||||
}
|
||||
|
||||
void render_context::set_viewport(f32 x, f32 y, f32 width, f32 height) {
|
||||
if (cmd_buffer_) {
|
||||
vk::Viewport viewport{};
|
||||
viewport.x = x;
|
||||
viewport.y = y;
|
||||
viewport.width = width;
|
||||
viewport.height = height;
|
||||
viewport.minDepth = 0.0f;
|
||||
viewport.maxDepth = 1.0f;
|
||||
cmd_buffer_.setViewport(0, 1, &viewport);
|
||||
}
|
||||
}
|
||||
|
||||
void render_context::set_scissor(i32 x, i32 y, u32 width, u32 height) {
|
||||
if (cmd_buffer_) {
|
||||
vk::Rect2D scissor{};
|
||||
scissor.offset = vk::Offset2D{x, y};
|
||||
scissor.extent = vk::Extent2D{width, height};
|
||||
cmd_buffer_.setScissor(0, 1, &scissor);
|
||||
}
|
||||
}
|
||||
|
||||
void render_context::draw(u32 vertex_count, u32 instance_count, u32 first_vertex, u32 first_instance) {
|
||||
if (cmd_buffer_) {
|
||||
cmd_buffer_.draw(vertex_count, instance_count, first_vertex, first_instance);
|
||||
}
|
||||
}
|
||||
|
||||
void render_context::draw_indexed(u32 index_count, u32 instance_count,
|
||||
u32 first_index, i32 vertex_offset, u32 first_instance) {
|
||||
if (cmd_buffer_) {
|
||||
cmd_buffer_.drawIndexed(index_count, instance_count, first_index, vertex_offset, first_instance);
|
||||
}
|
||||
}
|
||||
|
||||
void render_context::draw_fullscreen_quad() {
|
||||
// 绘制一个覆盖整个视口的三角形
|
||||
// 顶点: (-1, -1), (3, -1), (-1, 3)
|
||||
// 这样只需要一个三角形就能覆盖整个屏幕
|
||||
static const std::array<vec2, 3> vertices = {
|
||||
vec2{-1.0f, -1.0f},
|
||||
vec2{3.0f, -1.0f},
|
||||
vec2{-1.0f, 3.0f}
|
||||
};
|
||||
|
||||
// 这里需要先绑定顶点缓冲,暂时使用简单的绘制
|
||||
// 实际使用时应通过批处理渲染器或预创建的几何体
|
||||
draw(3, 1, 0, 0);
|
||||
}
|
||||
|
||||
void render_context::transition_image_layout(vk::Image image,
|
||||
vk::ImageLayout old_layout,
|
||||
vk::ImageLayout new_layout,
|
||||
vk::ImageAspectFlags aspect_mask) {
|
||||
if (!cmd_buffer_) return;
|
||||
|
||||
vk::ImageMemoryBarrier2 barrier{};
|
||||
barrier.oldLayout = old_layout;
|
||||
barrier.newLayout = new_layout;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = image;
|
||||
barrier.subresourceRange.aspectMask = aspect_mask;
|
||||
barrier.subresourceRange.baseMipLevel = 0;
|
||||
barrier.subresourceRange.levelCount = 1;
|
||||
barrier.subresourceRange.baseArrayLayer = 0;
|
||||
barrier.subresourceRange.layerCount = 1;
|
||||
|
||||
// 设置源和目标阶段/访问掩码
|
||||
if (old_layout == vk::ImageLayout::eUndefined &&
|
||||
new_layout == vk::ImageLayout::eColorAttachmentOptimal) {
|
||||
barrier.srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe;
|
||||
barrier.srcAccessMask = vk::AccessFlagBits2::eNone;
|
||||
barrier.dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite;
|
||||
} else if (old_layout == vk::ImageLayout::eColorAttachmentOptimal &&
|
||||
new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) {
|
||||
barrier.srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput;
|
||||
barrier.srcAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite;
|
||||
barrier.dstStageMask = vk::PipelineStageFlagBits2::eFragmentShader;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits2::eShaderRead;
|
||||
} else {
|
||||
barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
barrier.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite;
|
||||
barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
|
||||
}
|
||||
|
||||
vk::DependencyInfo dependency_info{};
|
||||
dependency_info.imageMemoryBarrierCount = 1;
|
||||
dependency_info.pImageMemoryBarriers = &barrier;
|
||||
|
||||
cmd_buffer_.pipelineBarrier2(&dependency_info);
|
||||
}
|
||||
|
||||
void render_context::image_barrier(const image_memory_barrier_config& config) {
|
||||
if (!cmd_buffer_) return;
|
||||
|
||||
vk::ImageMemoryBarrier2 barrier{};
|
||||
barrier.oldLayout = config.old_layout;
|
||||
barrier.newLayout = config.new_layout;
|
||||
barrier.srcQueueFamilyIndex = config.src_queue_family_index;
|
||||
barrier.dstQueueFamilyIndex = config.dst_queue_family_index;
|
||||
barrier.image = config.image;
|
||||
barrier.subresourceRange = config.subresource_range;
|
||||
|
||||
vk::DependencyInfo dependency_info{};
|
||||
dependency_info.imageMemoryBarrierCount = 1;
|
||||
dependency_info.pImageMemoryBarriers = &barrier;
|
||||
|
||||
cmd_buffer_.pipelineBarrier2(&dependency_info);
|
||||
}
|
||||
|
||||
} // namespace mirai
|
||||
312
src/render/render_context.hpp
Normal file
312
src/render/render_context.hpp
Normal file
@@ -0,0 +1,312 @@
|
||||
/**
|
||||
* @file render_context.hpp
|
||||
* @brief MIRAI 框架渲染上下文
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 提供渲染时访问 Vulkan 资源的接口,用于自定义渲染控件。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "vulkan_types.hpp"
|
||||
#include "renderer.hpp"
|
||||
#include "pipeline.hpp"
|
||||
#include "command_buffer.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// ================================================================================================
|
||||
// 前置声明
|
||||
// ================================================================================================
|
||||
|
||||
class render_context;
|
||||
|
||||
// ================================================================================================
|
||||
// 渲染阶段
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 渲染阶段类型
|
||||
*/
|
||||
enum class render_phase {
|
||||
/// 正常渲染阶段
|
||||
normal,
|
||||
/// 后效处理阶段(backdrop)
|
||||
backdrop,
|
||||
/// 遮罩阶段
|
||||
mask
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// 渲染上下文
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 渲染上下文
|
||||
*
|
||||
* 提供渲染时访问 Vulkan 资源的接口。
|
||||
* 控件可以通过此上下文获取渲染器、命令缓冲等进行自定义渲染。
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* void my_widget::on_paint(paint_context& ctx) {
|
||||
* auto* rc = ctx.get_render_context();
|
||||
* if (rc) {
|
||||
* auto cmd = rc->get_command_buffer();
|
||||
* // 自定义渲染命令
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
class render_context {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param renderer 渲染器
|
||||
* @param cmd_buffer 命令缓冲
|
||||
* @param phase 当前渲染阶段
|
||||
*/
|
||||
render_context(renderer* renderer, vk::CommandBuffer cmd_buffer, render_phase phase = render_phase::normal);
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~render_context();
|
||||
|
||||
// 禁止拷贝
|
||||
render_context(const render_context&) = delete;
|
||||
render_context& operator=(const render_context&) = delete;
|
||||
|
||||
// ============================================================================================
|
||||
// 资源访问
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取渲染器
|
||||
* @return 渲染器指针
|
||||
*/
|
||||
[[nodiscard]] renderer* get_renderer() const noexcept { return renderer_; }
|
||||
|
||||
/**
|
||||
* @brief 获取命令缓冲
|
||||
* @return Vulkan 命令缓冲
|
||||
*/
|
||||
[[nodiscard]] vk::CommandBuffer get_command_buffer() const noexcept { return cmd_buffer_; }
|
||||
|
||||
/**
|
||||
* @brief 获取渲染器
|
||||
* @return 渲染器引用
|
||||
*/
|
||||
[[nodiscard]] renderer& get_renderer_ref() const { return *renderer_; }
|
||||
|
||||
/**
|
||||
* @brief 获取批处理渲染器
|
||||
* @return 批处理渲染器指针
|
||||
*/
|
||||
[[nodiscard]] batch_renderer* get_batch_renderer() const;
|
||||
|
||||
/**
|
||||
* @brief 获取当前帧索引
|
||||
* @return 帧索引
|
||||
*/
|
||||
[[nodiscard]] u32 get_frame_index() const;
|
||||
|
||||
/**
|
||||
* @brief 获取当前图像索引
|
||||
* @return 图像索引
|
||||
*/
|
||||
[[nodiscard]] u32 get_image_index() const;
|
||||
|
||||
/**
|
||||
* @brief 获取交换链范围
|
||||
* @return 交换链范围
|
||||
*/
|
||||
[[nodiscard]] vk::Extent2D get_swapchain_extent() const;
|
||||
|
||||
/**
|
||||
* @brief 获取交换链格式
|
||||
* @return 交换链格式
|
||||
*/
|
||||
[[nodiscard]] vk::Format get_swapchain_format() const;
|
||||
|
||||
/**
|
||||
* @brief 获取深度格式
|
||||
* @return 深度格式
|
||||
*/
|
||||
[[nodiscard]] vk::Format get_depth_format() const;
|
||||
|
||||
// ============================================================================================
|
||||
// 渲染阶段管理
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取当前渲染阶段
|
||||
* @return 渲染阶段
|
||||
*/
|
||||
[[nodiscard]] render_phase get_phase() const noexcept { return phase_; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否为后效处理阶段
|
||||
* @return 如果是后效处理阶段返回 true
|
||||
*/
|
||||
[[nodiscard]] bool is_backdrop_phase() const noexcept { return phase_ == render_phase::backdrop; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否为遮罩阶段
|
||||
* @return 如果是遮罩阶段返回 true
|
||||
*/
|
||||
[[nodiscard]] bool is_mask_phase() const noexcept { return phase_ == render_phase::mask; }
|
||||
|
||||
// ============================================================================================
|
||||
// 渲染操作
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 绑定图形管线
|
||||
* @param pipeline 管线句柄
|
||||
*/
|
||||
void bind_pipeline(vk::Pipeline pipeline);
|
||||
|
||||
/**
|
||||
* @brief 绑定管线布局
|
||||
* @param layout 管线布局
|
||||
*/
|
||||
void bind_pipeline_layout(vk::PipelineLayout layout);
|
||||
|
||||
/**
|
||||
* @brief 绑定描述符集
|
||||
* @param first_set 第一个集索引
|
||||
* @param descriptor_sets 描述符集列表
|
||||
* @param dynamic_offsets 动态偏移量列表
|
||||
*/
|
||||
void bind_descriptor_sets(vk::PipelineBindPoint bind_point,
|
||||
vk::PipelineLayout layout,
|
||||
u32 first_set,
|
||||
std::span<const vk::DescriptorSet> descriptor_sets,
|
||||
std::span<const u32> dynamic_offsets = {});
|
||||
|
||||
/**
|
||||
* @brief 推送常量
|
||||
* @param stage_flags 着色器阶段标志
|
||||
* @param offset 偏移量
|
||||
* @param data 数据
|
||||
*/
|
||||
void push_constants(vk::ShaderStageFlags stage_flags, u32 offset, std::span<const u8> data);
|
||||
|
||||
/**
|
||||
* @brief 推送常量(模板版本)
|
||||
* @tparam T 数据类型
|
||||
* @param stage_flags 着色器阶段标志
|
||||
* @param data 数据
|
||||
* @param offset 偏移量
|
||||
*/
|
||||
template<typename T>
|
||||
void push_constants(vk::ShaderStageFlags stage_flags, const T& data, u32 offset = 0) {
|
||||
push_constants(stage_flags, offset, std::as_bytes(std::span(&data, 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置视口
|
||||
* @param x X 坐标
|
||||
* @param y Y 坐标
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
*/
|
||||
void set_viewport(f32 x, f32 y, f32 width, f32 height);
|
||||
|
||||
/**
|
||||
* @brief 设置裁剪矩形
|
||||
* @param x X 坐标
|
||||
* @param y Y 坐标
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
*/
|
||||
void set_scissor(i32 x, i32 y, u32 width, u32 height);
|
||||
|
||||
/**
|
||||
* @brief 绘制
|
||||
* @param vertex_count 顶点数量
|
||||
* @param instance_count 实例数量
|
||||
* @param first_vertex 第一个顶点
|
||||
* @param first_instance 第一个实例
|
||||
*/
|
||||
void draw(u32 vertex_count, u32 instance_count = 1, u32 first_vertex = 0, u32 first_instance = 0);
|
||||
|
||||
/**
|
||||
* @brief 索引绘制
|
||||
* @param index_count 索引数量
|
||||
* @param instance_count 实例数量
|
||||
* @param first_index 第一个索引
|
||||
* @param vertex_offset 顶点偏移
|
||||
* @param first_instance 第一个实例
|
||||
*/
|
||||
void draw_indexed(u32 index_count, u32 instance_count = 1,
|
||||
u32 first_index = 0, i32 vertex_offset = 0, u32 first_instance = 0);
|
||||
|
||||
/**
|
||||
* @brief 绘制全屏四边形
|
||||
*/
|
||||
void draw_fullscreen_quad();
|
||||
|
||||
// ============================================================================================
|
||||
// 图像操作
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 转换图像布局
|
||||
* @param image 图像
|
||||
* @param old_layout 旧布局
|
||||
* @param new_layout 新布局
|
||||
* @param aspect_mask 方面掩码
|
||||
*/
|
||||
void transition_image_layout(vk::Image image,
|
||||
vk::ImageLayout old_layout,
|
||||
vk::ImageLayout new_layout,
|
||||
vk::ImageAspectFlags aspect_mask = vk::ImageAspectFlagBits::eColor);
|
||||
|
||||
/**
|
||||
* @barrier 图像内存屏障
|
||||
* @param config 屏障配置
|
||||
*/
|
||||
void image_barrier(const image_memory_barrier_config& config);
|
||||
|
||||
private:
|
||||
/// 渲染器
|
||||
renderer* renderer_ = nullptr;
|
||||
|
||||
/// 命令缓冲
|
||||
vk::CommandBuffer cmd_buffer_ = VK_NULL_HANDLE;
|
||||
|
||||
/// 当前渲染阶段
|
||||
render_phase phase_ = render_phase::normal;
|
||||
|
||||
/// 当前绑定的管线布局
|
||||
vk::PipelineLayout current_pipeline_layout_ = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// 绘制上下文扩展
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 绘制上下文扩展接口
|
||||
*
|
||||
* 提供获取渲染上下文的能力。
|
||||
*/
|
||||
class paint_context_ex {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取渲染上下文
|
||||
* @return 渲染上下文指针,如果不可用返回 nullptr
|
||||
*/
|
||||
[[nodiscard]] virtual render_context* get_render_context() const = 0;
|
||||
|
||||
protected:
|
||||
~paint_context_ex() = default;
|
||||
};
|
||||
|
||||
} // namespace mirai
|
||||
@@ -5,6 +5,10 @@
|
||||
* @version 0.1.0
|
||||
*/
|
||||
|
||||
// 定义动态分发加载器的存储(必须在包含 vulkan.hpp 之前,且只能在一个编译单元中定义)
|
||||
#include "vulkan_types.hpp"
|
||||
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
|
||||
|
||||
#include "vulkan_instance.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
|
||||
@@ -6,6 +6,15 @@
|
||||
project(mirai_shader)
|
||||
simple_library(STATIC)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC mirai_core mirai_render mirai_resource Vulkan::Vulkan)
|
||||
|
||||
# 添加 GLSL 着色器编译到现有目标
|
||||
# 自动搜索 shaders/glsl 目录下的所有 .vert/.frag 文件
|
||||
add_glsl_shader_library(TARGET ${PROJECT_NAME}
|
||||
SHADER_PATH shaders/glsl
|
||||
INCLUDE_DIRS shaders/glsl
|
||||
RECURSIVE
|
||||
)
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR}
|
||||
@@ -18,4 +27,4 @@ install(TARGETS ${PROJECT_NAME}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
message(STATUS "Configured mirai_shader library")
|
||||
message(STATUS "Configured mirai_shader library with GLSL shaders")
|
||||
@@ -1,710 +0,0 @@
|
||||
/**
|
||||
* @file shader_cache.cpp
|
||||
* @brief MIRAI 框架着色器缓存系统实现
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*/
|
||||
|
||||
#include "shader_cache.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief 简单的 FNV-1a 哈希函数
|
||||
*/
|
||||
constexpr u64 fnv1a_hash(const char* data, size_t length) {
|
||||
constexpr u64 FNV_OFFSET = 14695981039346656037ULL;
|
||||
constexpr u64 FNV_PRIME = 1099511628211ULL;
|
||||
|
||||
u64 hash = FNV_OFFSET;
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
hash ^= static_cast<u64>(static_cast<u8>(data[i]));
|
||||
hash *= FNV_PRIME;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将哈希值转换为十六进制字符串
|
||||
*/
|
||||
std::string hash_to_hex(u64 hash) {
|
||||
std::ostringstream oss;
|
||||
oss << std::hex << std::setfill('0') << std::setw(16) << hash;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 缓存文件魔数
|
||||
*/
|
||||
constexpr u32 CACHE_MAGIC = 0x4D494C43; // "MILC"
|
||||
|
||||
/**
|
||||
* @brief 缓存文件头
|
||||
*/
|
||||
struct cache_file_header {
|
||||
u32 magic{CACHE_MAGIC};
|
||||
u32 version{1};
|
||||
u32 spirv_size{0};
|
||||
u32 stage{0};
|
||||
u64 options_hash{0};
|
||||
u64 created_timestamp{0};
|
||||
u32 entry_point_length{0};
|
||||
u32 reserved{0};
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// ================================================================================================
|
||||
// shader_cache 实现
|
||||
// ================================================================================================
|
||||
|
||||
shader_cache::shader_cache() : config_() {}
|
||||
|
||||
shader_cache::shader_cache(const shader_cache_config& config) : config_(config) {
|
||||
if (config_.enable_disk_cache && config_.load_on_startup) {
|
||||
load_from_disk();
|
||||
}
|
||||
}
|
||||
|
||||
shader_cache::~shader_cache() {
|
||||
if (config_.enable_disk_cache && config_.save_on_shutdown) {
|
||||
save_to_disk();
|
||||
}
|
||||
}
|
||||
|
||||
shader_cache::shader_cache(shader_cache&& other) noexcept
|
||||
: config_(std::move(other.config_))
|
||||
, memory_cache_(std::move(other.memory_cache_))
|
||||
, stats_(other.stats_) {}
|
||||
|
||||
shader_cache& shader_cache::operator=(shader_cache&& other) noexcept {
|
||||
if (this != &other) {
|
||||
config_ = std::move(other.config_);
|
||||
memory_cache_ = std::move(other.memory_cache_);
|
||||
stats_ = other.stats_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::optional<shader_cache_entry> shader_cache::get(
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options) {
|
||||
|
||||
std::string key = generate_key(source, stage, options);
|
||||
return get_by_key(key);
|
||||
}
|
||||
|
||||
std::optional<shader_cache_entry> shader_cache::get_by_key(std::string_view key) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// 先在内存缓存中查找
|
||||
if (config_.enable_memory_cache) {
|
||||
auto it = memory_cache_.find(std::string(key));
|
||||
if (it != memory_cache_.end()) {
|
||||
if (!is_expired(it->second)) {
|
||||
update_access_info(it->second);
|
||||
++stats_.hits;
|
||||
// 构造返回值,reflection 需要重新执行(因为 shader_reflection 不可拷贝)
|
||||
shader_cache_entry result;
|
||||
result.metadata = it->second.metadata;
|
||||
result.spirv = it->second.spirv;
|
||||
result.valid = it->second.valid;
|
||||
if (!result.spirv.empty()) {
|
||||
(void)result.reflection.reflect(result.spirv);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
// 过期,移除
|
||||
memory_cache_.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在磁盘缓存中查找
|
||||
if (config_.enable_disk_cache) {
|
||||
auto entry = load_entry_from_disk(key);
|
||||
if (entry.has_value() && !is_expired(*entry)) {
|
||||
++stats_.hits;
|
||||
// 加载到内存缓存(移动进去)
|
||||
if (config_.enable_memory_cache) {
|
||||
memory_cache_.insert_or_assign(std::string(key), std::move(*entry));
|
||||
// 从缓存返回副本
|
||||
auto& cached = memory_cache_.at(std::string(key));
|
||||
shader_cache_entry result;
|
||||
result.metadata = cached.metadata;
|
||||
result.spirv = cached.spirv;
|
||||
result.valid = cached.valid;
|
||||
if (!result.spirv.empty()) {
|
||||
(void)result.reflection.reflect(result.spirv);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
++stats_.misses;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void shader_cache::put(
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options,
|
||||
const shader_compile_result& result) {
|
||||
|
||||
if (!result.success || result.spirv.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string key = generate_key(source, stage, options);
|
||||
|
||||
shader_cache_entry entry;
|
||||
entry.metadata.key = key;
|
||||
entry.metadata.stage = stage;
|
||||
entry.metadata.entry_point = options.entry_point;
|
||||
entry.metadata.created_at = std::chrono::system_clock::now();
|
||||
entry.metadata.last_accessed = entry.metadata.created_at;
|
||||
entry.metadata.access_count = 1;
|
||||
entry.metadata.spirv_size = result.spirv.size() * sizeof(u32);
|
||||
entry.metadata.options_hash = hash_options(options);
|
||||
entry.metadata.dependencies = result.dependencies;
|
||||
entry.spirv = result.spirv;
|
||||
// 从 spirv 重新执行反射(shader_reflection 不可拷贝)
|
||||
if (!entry.spirv.empty()) {
|
||||
(void)entry.reflection.reflect(entry.spirv);
|
||||
}
|
||||
entry.valid = true;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// 检查是否需要清理
|
||||
if (config_.max_entries > 0 && memory_cache_.size() >= config_.max_entries) {
|
||||
cleanup_to_fit();
|
||||
}
|
||||
|
||||
// 存入内存缓存
|
||||
if (config_.enable_memory_cache) {
|
||||
memory_cache_.emplace(key, std::move(entry));
|
||||
++stats_.entry_count;
|
||||
stats_.total_size += result.spirv.size() * sizeof(u32);
|
||||
}
|
||||
|
||||
// 存入磁盘缓存
|
||||
if (config_.enable_disk_cache) {
|
||||
save_entry_to_disk(entry);
|
||||
}
|
||||
}
|
||||
|
||||
shader_compile_result shader_cache::get_or_compile(
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options,
|
||||
shader_compiler& compiler) {
|
||||
|
||||
// 尝试从缓存获取
|
||||
auto cached = get(source, stage, options);
|
||||
if (cached) {
|
||||
shader_compile_result result;
|
||||
result.success = true;
|
||||
result.spirv = std::move(cached->spirv);
|
||||
result.reflection = std::move(cached->reflection);
|
||||
result.dependencies = std::move(cached->metadata.dependencies);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 编译
|
||||
auto result = compiler.compile_from_source(source, stage, options);
|
||||
|
||||
// 缓存成功的结果
|
||||
if (result.success) {
|
||||
put(source, stage, options, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool shader_cache::invalidate(std::string_view key) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
bool removed = false;
|
||||
|
||||
// 从内存缓存移除
|
||||
auto it = memory_cache_.find(std::string(key));
|
||||
if (it != memory_cache_.end()) {
|
||||
stats_.total_size -= it->second.metadata.spirv_size;
|
||||
--stats_.entry_count;
|
||||
memory_cache_.erase(it);
|
||||
removed = true;
|
||||
}
|
||||
|
||||
// 从磁盘缓存移除
|
||||
if (config_.enable_disk_cache) {
|
||||
auto path = get_cache_file_path(key);
|
||||
if (std::filesystem::exists(path)) {
|
||||
std::filesystem::remove(path);
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
u32 shader_cache::invalidate(const std::vector<std::string>& keys) {
|
||||
u32 count = 0;
|
||||
for (const auto& key : keys) {
|
||||
if (invalidate(key)) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
u32 shader_cache::invalidate_by_path(const std::filesystem::path& path) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
std::vector<std::string> keys_to_remove;
|
||||
|
||||
for (const auto& [key, entry] : memory_cache_) {
|
||||
// 检查源文件路径
|
||||
if (entry.metadata.source_path == path) {
|
||||
keys_to_remove.push_back(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查依赖
|
||||
for (const auto& dep : entry.metadata.dependencies) {
|
||||
if (dep == path) {
|
||||
keys_to_remove.push_back(key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 count = 0;
|
||||
for (const auto& key : keys_to_remove) {
|
||||
auto it = memory_cache_.find(key);
|
||||
if (it != memory_cache_.end()) {
|
||||
stats_.total_size -= it->second.metadata.spirv_size;
|
||||
--stats_.entry_count;
|
||||
memory_cache_.erase(it);
|
||||
++count;
|
||||
|
||||
// 从磁盘移除
|
||||
if (config_.enable_disk_cache) {
|
||||
auto cache_path = get_cache_file_path(key);
|
||||
if (std::filesystem::exists(cache_path)) {
|
||||
std::filesystem::remove(cache_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void shader_cache::clear() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
memory_cache_.clear();
|
||||
stats_.entry_count = 0;
|
||||
stats_.total_size = 0;
|
||||
|
||||
// 清空磁盘缓存
|
||||
if (config_.enable_disk_cache && std::filesystem::exists(config_.cache_directory)) {
|
||||
std::filesystem::remove_all(config_.cache_directory);
|
||||
}
|
||||
}
|
||||
|
||||
bool shader_cache::contains(
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options) const {
|
||||
|
||||
std::string key = generate_key(source, stage, options);
|
||||
return contains_key(key);
|
||||
}
|
||||
|
||||
bool shader_cache::contains_key(std::string_view key) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (config_.enable_memory_cache) {
|
||||
if (memory_cache_.find(std::string(key)) != memory_cache_.end()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (config_.enable_disk_cache) {
|
||||
auto path = get_cache_file_path(key);
|
||||
return std::filesystem::exists(path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
shader_cache_stats shader_cache::get_stats() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
shader_cache_stats current_stats = stats_;
|
||||
current_stats.entry_count = static_cast<u32>(memory_cache_.size());
|
||||
current_stats.total_size = calculate_memory_usage();
|
||||
|
||||
// 计算磁盘大小
|
||||
if (config_.enable_disk_cache && std::filesystem::exists(config_.cache_directory)) {
|
||||
current_stats.disk_size = 0;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(config_.cache_directory)) {
|
||||
if (entry.is_regular_file()) {
|
||||
current_stats.disk_size += entry.file_size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return current_stats;
|
||||
}
|
||||
|
||||
std::vector<std::string> shader_cache::get_keys() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
std::vector<std::string> keys;
|
||||
keys.reserve(memory_cache_.size());
|
||||
|
||||
for (const auto& [key, entry] : memory_cache_) {
|
||||
keys.push_back(key);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
std::optional<shader_cache_entry_metadata> shader_cache::get_metadata(std::string_view key) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto it = memory_cache_.find(std::string(key));
|
||||
if (it != memory_cache_.end()) {
|
||||
return it->second.metadata;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool shader_cache::save_to_disk() {
|
||||
if (!config_.enable_disk_cache) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// 确保缓存目录存在
|
||||
if (!std::filesystem::exists(config_.cache_directory)) {
|
||||
std::filesystem::create_directories(config_.cache_directory);
|
||||
}
|
||||
|
||||
bool all_success = true;
|
||||
for (const auto& [key, entry] : memory_cache_) {
|
||||
if (!save_entry_to_disk(entry)) {
|
||||
all_success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return all_success;
|
||||
}
|
||||
|
||||
u32 shader_cache::load_from_disk() {
|
||||
if (!config_.enable_disk_cache) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(config_.cache_directory)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
u32 loaded = 0;
|
||||
|
||||
for (const auto& file_entry : std::filesystem::directory_iterator(config_.cache_directory)) {
|
||||
if (!file_entry.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string filename = file_entry.path().filename().string();
|
||||
if (filename.size() < 4 || filename.substr(filename.size() - 4) != ".spv") {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string key = filename.substr(0, filename.size() - 4);
|
||||
|
||||
auto entry = load_entry_from_disk(key);
|
||||
if (entry.has_value() && !is_expired(entry.value())) {
|
||||
memory_cache_.insert_or_assign(key, std::move(entry.value()));
|
||||
++loaded;
|
||||
}
|
||||
}
|
||||
|
||||
stats_.entry_count = static_cast<u32>(memory_cache_.size());
|
||||
stats_.total_size = calculate_memory_usage();
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
u32 shader_cache::cleanup_expired() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
std::vector<std::string> expired_keys;
|
||||
|
||||
for (const auto& [key, entry] : memory_cache_) {
|
||||
if (is_expired(entry)) {
|
||||
expired_keys.push_back(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& key : expired_keys) {
|
||||
auto it = memory_cache_.find(key);
|
||||
if (it != memory_cache_.end()) {
|
||||
stats_.total_size -= it->second.metadata.spirv_size;
|
||||
memory_cache_.erase(it);
|
||||
}
|
||||
|
||||
if (config_.enable_disk_cache) {
|
||||
auto path = get_cache_file_path(key);
|
||||
if (std::filesystem::exists(path)) {
|
||||
std::filesystem::remove(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stats_.entry_count = static_cast<u32>(memory_cache_.size());
|
||||
|
||||
return static_cast<u32>(expired_keys.size());
|
||||
}
|
||||
|
||||
u32 shader_cache::cleanup_to_fit() {
|
||||
// 清理过期条目
|
||||
u32 cleaned = cleanup_expired();
|
||||
|
||||
// 检查条目数限制
|
||||
if (config_.max_entries > 0 && memory_cache_.size() > config_.max_entries) {
|
||||
u32 to_remove = static_cast<u32>(memory_cache_.size()) - config_.max_entries;
|
||||
cleaned += evict_entries(to_remove);
|
||||
}
|
||||
|
||||
// 检查大小限制
|
||||
if (config_.max_memory_size > 0) {
|
||||
size_t current_size = calculate_memory_usage();
|
||||
if (current_size > config_.max_memory_size) {
|
||||
cleaned += evict_entries(config_.max_memory_size);
|
||||
}
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
void shader_cache::set_config(const shader_cache_config& config) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
config_ = config;
|
||||
}
|
||||
|
||||
std::string shader_cache::generate_key(
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options) {
|
||||
|
||||
// 组合源码、阶段和选项生成唯一键
|
||||
u64 source_hash = hash_string(source);
|
||||
u64 options_hash = hash_options(options);
|
||||
u64 combined = source_hash ^ (options_hash << 1) ^ (static_cast<u64>(stage) << 57);
|
||||
|
||||
return hash_to_hex(combined);
|
||||
}
|
||||
|
||||
u64 shader_cache::hash_string(std::string_view str) {
|
||||
return fnv1a_hash(str.data(), str.size());
|
||||
}
|
||||
|
||||
u64 shader_cache::hash_options(const shader_compile_options& options) {
|
||||
std::ostringstream oss;
|
||||
|
||||
oss << static_cast<int>(options.target_api);
|
||||
oss << static_cast<int>(options.source_language);
|
||||
oss << static_cast<int>(options.optimization);
|
||||
oss << options.generate_debug_info;
|
||||
oss << options.enable_16bit_types;
|
||||
oss << options.entry_point;
|
||||
oss << options.glsl_version;
|
||||
oss << options.vulkan_semantics;
|
||||
|
||||
for (const auto& [name, value] : options.defines) {
|
||||
oss << name << "=" << value << ";";
|
||||
}
|
||||
|
||||
std::string combined = oss.str();
|
||||
return hash_string(combined);
|
||||
}
|
||||
|
||||
std::filesystem::path shader_cache::get_cache_file_path(std::string_view key) const {
|
||||
return config_.cache_directory / (std::string(key) + ".spv");
|
||||
}
|
||||
|
||||
bool shader_cache::save_entry_to_disk(const shader_cache_entry& entry) {
|
||||
if (!config_.enable_disk_cache) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保目录存在
|
||||
if (!std::filesystem::exists(config_.cache_directory)) {
|
||||
std::filesystem::create_directories(config_.cache_directory);
|
||||
}
|
||||
|
||||
auto path = get_cache_file_path(entry.metadata.key);
|
||||
std::ofstream file(path, std::ios::binary);
|
||||
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 写入文件头
|
||||
cache_file_header header;
|
||||
header.spirv_size = static_cast<u32>(entry.spirv.size());
|
||||
header.stage = static_cast<u32>(entry.metadata.stage);
|
||||
header.options_hash = entry.metadata.options_hash;
|
||||
header.created_timestamp = static_cast<u64>(
|
||||
std::chrono::duration_cast<std::chrono::seconds>(
|
||||
entry.metadata.created_at.time_since_epoch()
|
||||
).count()
|
||||
);
|
||||
header.entry_point_length = static_cast<u32>(entry.metadata.entry_point.size());
|
||||
|
||||
file.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
|
||||
// 写入入口点
|
||||
file.write(entry.metadata.entry_point.data(), entry.metadata.entry_point.size());
|
||||
|
||||
// 写入 SPIR-V
|
||||
file.write(reinterpret_cast<const char*>(entry.spirv.data()),
|
||||
entry.spirv.size() * sizeof(u32));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<shader_cache_entry> shader_cache::load_entry_from_disk(std::string_view key) {
|
||||
auto path = get_cache_file_path(key);
|
||||
|
||||
if (!std::filesystem::exists(path)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 读取文件头
|
||||
cache_file_header header;
|
||||
file.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
|
||||
if (header.magic != CACHE_MAGIC || header.version != config_.cache_version) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
shader_cache_entry entry;
|
||||
entry.metadata.key = std::string(key);
|
||||
entry.metadata.stage = static_cast<shader_stage>(header.stage);
|
||||
entry.metadata.options_hash = header.options_hash;
|
||||
entry.metadata.created_at = std::chrono::system_clock::time_point(
|
||||
std::chrono::seconds(header.created_timestamp)
|
||||
);
|
||||
entry.metadata.last_accessed = std::chrono::system_clock::now();
|
||||
entry.metadata.spirv_size = header.spirv_size * sizeof(u32);
|
||||
|
||||
// 读取入口点
|
||||
entry.metadata.entry_point.resize(header.entry_point_length);
|
||||
file.read(entry.metadata.entry_point.data(), header.entry_point_length);
|
||||
|
||||
// 读取 SPIR-V
|
||||
entry.spirv.resize(header.spirv_size);
|
||||
file.read(reinterpret_cast<char*>(entry.spirv.data()),
|
||||
header.spirv_size * sizeof(u32));
|
||||
|
||||
// 执行反射(处理 [[nodiscard]] 返回值)
|
||||
bool reflect_success = entry.reflection.reflect(entry.spirv);
|
||||
entry.valid = reflect_success && entry.reflection.is_valid();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
void shader_cache::update_access_info(shader_cache_entry& entry) {
|
||||
entry.metadata.last_accessed = std::chrono::system_clock::now();
|
||||
++entry.metadata.access_count;
|
||||
}
|
||||
|
||||
bool shader_cache::is_expired(const shader_cache_entry& entry) const {
|
||||
if (config_.expiration_seconds == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto age = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
now - entry.metadata.created_at
|
||||
);
|
||||
|
||||
return age.count() > config_.expiration_seconds;
|
||||
}
|
||||
|
||||
size_t shader_cache::calculate_memory_usage() const {
|
||||
size_t total = 0;
|
||||
for (const auto& [key, entry] : memory_cache_) {
|
||||
total += entry.metadata.spirv_size;
|
||||
total += key.size();
|
||||
total += entry.metadata.entry_point.size();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
u32 shader_cache::evict_entries(size_t target) {
|
||||
if (memory_cache_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 按最后访问时间排序
|
||||
std::vector<std::pair<std::string, std::chrono::system_clock::time_point>> entries;
|
||||
entries.reserve(memory_cache_.size());
|
||||
|
||||
for (const auto& [key, entry] : memory_cache_) {
|
||||
entries.emplace_back(key, entry.metadata.last_accessed);
|
||||
}
|
||||
|
||||
std::ranges::sort(entries, [](const auto& a, const auto& b) {
|
||||
return a.second < b.second; // 最旧的在前
|
||||
});
|
||||
|
||||
u32 evicted = 0;
|
||||
size_t current_size = calculate_memory_usage();
|
||||
|
||||
for (const auto& [key, time] : entries) {
|
||||
if (config_.max_entries > 0 && memory_cache_.size() <= config_.max_entries &&
|
||||
(config_.max_memory_size == 0 || current_size <= config_.max_memory_size)) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto it = memory_cache_.find(key);
|
||||
if (it != memory_cache_.end()) {
|
||||
current_size -= it->second.metadata.spirv_size;
|
||||
memory_cache_.erase(it);
|
||||
++evicted;
|
||||
}
|
||||
}
|
||||
|
||||
stats_.entry_count = static_cast<u32>(memory_cache_.size());
|
||||
stats_.total_size = current_size;
|
||||
|
||||
return evicted;
|
||||
}
|
||||
|
||||
} // namespace mirai
|
||||
@@ -1,441 +0,0 @@
|
||||
/**
|
||||
* @file shader_cache.hpp
|
||||
* @brief MIRAI 框架着色器缓存系统
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* @details
|
||||
* 本文件实现了着色器缓存系统,提供:
|
||||
* - 编译后 SPIR-V 的内存缓存
|
||||
* - 磁盘持久化
|
||||
* - 基于源码哈希的缓存键
|
||||
* - 缓存版本控制
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shader_types.hpp"
|
||||
#include "shader_compiler.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
/**
|
||||
* @brief 缓存条目元数据
|
||||
*/
|
||||
struct shader_cache_entry_metadata {
|
||||
/// 缓存键(源码哈希)
|
||||
std::string key;
|
||||
|
||||
/// 着色器阶段
|
||||
shader_stage stage{shader_stage::vertex};
|
||||
|
||||
/// 入口点
|
||||
std::string entry_point{"main"};
|
||||
|
||||
/// 源文件路径
|
||||
std::filesystem::path source_path;
|
||||
|
||||
/// 依赖文件列表
|
||||
std::vector<std::filesystem::path> dependencies;
|
||||
|
||||
/// 创建时间
|
||||
std::chrono::system_clock::time_point created_at;
|
||||
|
||||
/// 最后访问时间
|
||||
std::chrono::system_clock::time_point last_accessed;
|
||||
|
||||
/// 访问次数
|
||||
u32 access_count{0};
|
||||
|
||||
/// SPIR-V 大小(字节)
|
||||
size_t spirv_size{0};
|
||||
|
||||
/// 编译选项哈希
|
||||
u64 options_hash{0};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 着色器缓存条目
|
||||
*/
|
||||
struct shader_cache_entry {
|
||||
/// 元数据
|
||||
shader_cache_entry_metadata metadata;
|
||||
|
||||
/// SPIR-V 字节码
|
||||
std::vector<u32> spirv;
|
||||
|
||||
/// 反射信息
|
||||
shader_reflection reflection;
|
||||
|
||||
/// 是否有效
|
||||
bool valid{false};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 缓存统计信息
|
||||
*/
|
||||
struct shader_cache_stats {
|
||||
/// 缓存命中次数
|
||||
u64 hits{0};
|
||||
|
||||
/// 缓存未命中次数
|
||||
u64 misses{0};
|
||||
|
||||
/// 当前缓存条目数
|
||||
u32 entry_count{0};
|
||||
|
||||
/// 缓存总大小(字节)
|
||||
size_t total_size{0};
|
||||
|
||||
/// 磁盘缓存大小(字节)
|
||||
size_t disk_size{0};
|
||||
|
||||
/**
|
||||
* @brief 获取命中率
|
||||
* @return 命中率(0-1)
|
||||
*/
|
||||
[[nodiscard]] f64 get_hit_rate() const noexcept {
|
||||
u64 total = hits + misses;
|
||||
return (total > 0) ? static_cast<f64>(hits) / static_cast<f64>(total) : 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 缓存配置
|
||||
*/
|
||||
struct shader_cache_config {
|
||||
/// 是否启用内存缓存
|
||||
bool enable_memory_cache{true};
|
||||
|
||||
/// 是否启用磁盘缓存
|
||||
bool enable_disk_cache{true};
|
||||
|
||||
/// 磁盘缓存目录
|
||||
std::filesystem::path cache_directory{"shader_cache"};
|
||||
|
||||
/// 最大内存缓存大小(字节,0表示无限制)
|
||||
size_t max_memory_size{0};
|
||||
|
||||
/// 最大磁盘缓存大小(字节,0表示无限制)
|
||||
size_t max_disk_size{0};
|
||||
|
||||
/// 最大缓存条目数(0表示无限制)
|
||||
u32 max_entries{0};
|
||||
|
||||
/// 缓存版本(用于使旧缓存失效)
|
||||
u32 cache_version{1};
|
||||
|
||||
/// 是否在启动时加载磁盘缓存
|
||||
bool load_on_startup{true};
|
||||
|
||||
/// 是否在关闭时保存到磁盘
|
||||
bool save_on_shutdown{true};
|
||||
|
||||
/// 条目过期时间(秒,0表示永不过期)
|
||||
u32 expiration_seconds{0};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 着色器缓存
|
||||
*
|
||||
* @details
|
||||
* 提供着色器编译结果的缓存功能,支持内存缓存和磁盘持久化。
|
||||
*
|
||||
* 使用示例:
|
||||
* @code
|
||||
* shader_cache_config config;
|
||||
* config.cache_directory = "my_cache";
|
||||
* config.enable_disk_cache = true;
|
||||
*
|
||||
* shader_cache cache(config);
|
||||
*
|
||||
* // 尝试从缓存获取
|
||||
* auto entry = cache.get(source_code, stage, options);
|
||||
* if (entry) {
|
||||
* // 使用缓存的 SPIR-V
|
||||
* } else {
|
||||
* // 编译并缓存
|
||||
* auto result = compiler.compile_from_source(source_code, stage, options);
|
||||
* cache.put(source_code, stage, options, result);
|
||||
* }
|
||||
*
|
||||
* // 或使用 get_or_compile
|
||||
* auto result = cache.get_or_compile(source_code, stage, options, compiler);
|
||||
* @endcode
|
||||
*/
|
||||
class shader_cache {
|
||||
public:
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
shader_cache();
|
||||
|
||||
/**
|
||||
* @brief 带配置的构造函数
|
||||
* @param config 缓存配置
|
||||
*/
|
||||
explicit shader_cache(const shader_cache_config& config);
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~shader_cache();
|
||||
|
||||
// 禁用拷贝
|
||||
shader_cache(const shader_cache&) = delete;
|
||||
shader_cache& operator=(const shader_cache&) = delete;
|
||||
|
||||
// 允许移动
|
||||
shader_cache(shader_cache&& other) noexcept;
|
||||
shader_cache& operator=(shader_cache&& other) noexcept;
|
||||
|
||||
/**
|
||||
* @brief 从缓存获取条目
|
||||
* @param source 着色器源码
|
||||
* @param stage 着色器阶段
|
||||
* @param options 编译选项
|
||||
* @return 缓存条目,未找到返回空
|
||||
*/
|
||||
[[nodiscard]] std::optional<shader_cache_entry> get(
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options = {}
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 按键获取缓存条目
|
||||
* @param key 缓存键
|
||||
* @return 缓存条目,未找到返回空
|
||||
*/
|
||||
[[nodiscard]] std::optional<shader_cache_entry> get_by_key(std::string_view key);
|
||||
|
||||
/**
|
||||
* @brief 存入缓存
|
||||
* @param source 着色器源码
|
||||
* @param stage 着色器阶段
|
||||
* @param options 编译选项
|
||||
* @param result 编译结果
|
||||
*/
|
||||
void put(
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options,
|
||||
const shader_compile_result& result
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 获取或编译
|
||||
* @param source 着色器源码
|
||||
* @param stage 着色器阶段
|
||||
* @param options 编译选项
|
||||
* @param compiler 编译器引用
|
||||
* @return 编译结果
|
||||
*/
|
||||
[[nodiscard]] shader_compile_result get_or_compile(
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options,
|
||||
shader_compiler& compiler
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 使缓存条目失效
|
||||
* @param key 缓存键
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool invalidate(std::string_view key);
|
||||
|
||||
/**
|
||||
* @brief 使多个缓存条目失效
|
||||
* @param keys 缓存键列表
|
||||
* @return 失效的条目数
|
||||
*/
|
||||
u32 invalidate(const std::vector<std::string>& keys);
|
||||
|
||||
/**
|
||||
* @brief 根据源文件路径使缓存失效
|
||||
* @param path 源文件路径
|
||||
* @return 失效的条目数
|
||||
*/
|
||||
u32 invalidate_by_path(const std::filesystem::path& path);
|
||||
|
||||
/**
|
||||
* @brief 清空所有缓存
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 检查是否有缓存
|
||||
* @param source 着色器源码
|
||||
* @param stage 着色器阶段
|
||||
* @param options 编译选项
|
||||
* @return 是否有缓存
|
||||
*/
|
||||
[[nodiscard]] bool contains(
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options = {}
|
||||
) const;
|
||||
|
||||
/**
|
||||
* @brief 按键检查是否有缓存
|
||||
* @param key 缓存键
|
||||
* @return 是否有缓存
|
||||
*/
|
||||
[[nodiscard]] bool contains_key(std::string_view key) const;
|
||||
|
||||
/**
|
||||
* @brief 获取缓存统计信息
|
||||
* @return 统计信息
|
||||
*/
|
||||
[[nodiscard]] shader_cache_stats get_stats() const;
|
||||
|
||||
/**
|
||||
* @brief 获取所有缓存键
|
||||
* @return 键列表
|
||||
*/
|
||||
[[nodiscard]] std::vector<std::string> get_keys() const;
|
||||
|
||||
/**
|
||||
* @brief 获取缓存条目元数据
|
||||
* @param key 缓存键
|
||||
* @return 元数据,未找到返回空
|
||||
*/
|
||||
[[nodiscard]] std::optional<shader_cache_entry_metadata> get_metadata(std::string_view key) const;
|
||||
|
||||
/**
|
||||
* @brief 保存缓存到磁盘
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool save_to_disk();
|
||||
|
||||
/**
|
||||
* @brief 从磁盘加载缓存
|
||||
* @return 加载的条目数
|
||||
*/
|
||||
u32 load_from_disk();
|
||||
|
||||
/**
|
||||
* @brief 清理过期条目
|
||||
* @return 清理的条目数
|
||||
*/
|
||||
u32 cleanup_expired();
|
||||
|
||||
/**
|
||||
* @brief 清理以满足大小限制
|
||||
* @return 清理的条目数
|
||||
*/
|
||||
u32 cleanup_to_fit();
|
||||
|
||||
/**
|
||||
* @brief 获取配置
|
||||
* @return 配置引用
|
||||
*/
|
||||
[[nodiscard]] const shader_cache_config& get_config() const noexcept { return config_; }
|
||||
|
||||
/**
|
||||
* @brief 设置配置
|
||||
* @param config 新配置
|
||||
*/
|
||||
void set_config(const shader_cache_config& config);
|
||||
|
||||
/**
|
||||
* @brief 生成缓存键
|
||||
* @param source 着色器源码
|
||||
* @param stage 着色器阶段
|
||||
* @param options 编译选项
|
||||
* @return 缓存键
|
||||
*/
|
||||
[[nodiscard]] static std::string generate_key(
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 计算字符串哈希
|
||||
* @param str 字符串
|
||||
* @return 哈希值
|
||||
*/
|
||||
[[nodiscard]] static u64 hash_string(std::string_view str);
|
||||
|
||||
/**
|
||||
* @brief 计算编译选项哈希
|
||||
* @param options 编译选项
|
||||
* @return 哈希值
|
||||
*/
|
||||
[[nodiscard]] static u64 hash_options(const shader_compile_options& options);
|
||||
|
||||
private:
|
||||
/// 配置
|
||||
shader_cache_config config_;
|
||||
|
||||
/// 内存缓存
|
||||
std::unordered_map<std::string, shader_cache_entry> memory_cache_;
|
||||
|
||||
/// 统计信息
|
||||
mutable shader_cache_stats stats_;
|
||||
|
||||
/// 互斥锁
|
||||
mutable std::mutex mutex_;
|
||||
|
||||
/**
|
||||
* @brief 获取磁盘缓存文件路径
|
||||
* @param key 缓存键
|
||||
* @return 文件路径
|
||||
*/
|
||||
[[nodiscard]] std::filesystem::path get_cache_file_path(std::string_view key) const;
|
||||
|
||||
/**
|
||||
* @brief 保存条目到磁盘
|
||||
* @param entry 缓存条目
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool save_entry_to_disk(const shader_cache_entry& entry);
|
||||
|
||||
/**
|
||||
* @brief 从磁盘加载条目
|
||||
* @param key 缓存键
|
||||
* @return 缓存条目,失败返回空
|
||||
*/
|
||||
[[nodiscard]] std::optional<shader_cache_entry> load_entry_from_disk(std::string_view key);
|
||||
|
||||
/**
|
||||
* @brief 更新访问信息
|
||||
* @param entry 缓存条目
|
||||
*/
|
||||
void update_access_info(shader_cache_entry& entry);
|
||||
|
||||
/**
|
||||
* @brief 检查条目是否过期
|
||||
* @param entry 缓存条目
|
||||
* @return 是否过期
|
||||
*/
|
||||
[[nodiscard]] bool is_expired(const shader_cache_entry& entry) const;
|
||||
|
||||
/**
|
||||
* @brief 计算当前内存使用量
|
||||
* @return 内存使用量(字节)
|
||||
*/
|
||||
[[nodiscard]] size_t calculate_memory_usage() const;
|
||||
|
||||
/**
|
||||
* @brief 淘汰条目(LRU策略)
|
||||
* @param target_size 目标大小
|
||||
* @return 淘汰的条目数
|
||||
*/
|
||||
u32 evict_entries(size_t target_size);
|
||||
};
|
||||
|
||||
} // namespace mirai
|
||||
@@ -1,501 +0,0 @@
|
||||
/**
|
||||
* @file shader_compiler.hpp
|
||||
* @brief MIRAI 框架着色器编译器封装
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* @details
|
||||
* 本文件实现了 Slang 着色器编译器的封装,提供:
|
||||
* - GLSL/HLSL/Slang 源码编译为 SPIR-V
|
||||
* - 预处理器支持(宏定义、include 处理)
|
||||
* - 编译错误报告(行号、列号、消息)
|
||||
* - SPIR-V 验证
|
||||
* - 条件编译支持(当 Slang 不可用时使用 glslang)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shader_types.hpp"
|
||||
#include "shader_reflection.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
/**
|
||||
* @brief 着色器源语言
|
||||
*/
|
||||
enum class shader_source_language : u8 {
|
||||
glsl, ///< GLSL 着色器语言
|
||||
hlsl, ///< HLSL 着色器语言
|
||||
slang, ///< Slang 着色器语言
|
||||
spirv, ///< 预编译的 SPIR-V
|
||||
auto_detect ///< 自动检测语言
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 着色器目标 API
|
||||
*/
|
||||
enum class shader_target_api : u8 {
|
||||
vulkan_1_0, ///< Vulkan 1.0
|
||||
vulkan_1_1, ///< Vulkan 1.1
|
||||
vulkan_1_2, ///< Vulkan 1.2
|
||||
vulkan_1_3 ///< Vulkan 1.3(默认)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 编译优化级别
|
||||
*/
|
||||
enum class shader_optimization_level : u8 {
|
||||
none, ///< 无优化(用于调试)
|
||||
size, ///< 优化代码大小
|
||||
performance ///< 优化性能(默认)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 着色器编译选项
|
||||
*/
|
||||
struct shader_compile_options {
|
||||
/// 目标 API
|
||||
shader_target_api target_api{shader_target_api::vulkan_1_3};
|
||||
|
||||
/// 源语言
|
||||
shader_source_language source_language{shader_source_language::auto_detect};
|
||||
|
||||
/// 优化级别
|
||||
shader_optimization_level optimization{shader_optimization_level::performance};
|
||||
|
||||
/// 是否生成调试信息
|
||||
bool generate_debug_info{false};
|
||||
|
||||
/// 是否启用 16 位类型
|
||||
bool enable_16bit_types{false};
|
||||
|
||||
/// 是否启用行指令
|
||||
bool enable_line_directives{true};
|
||||
|
||||
/// 预处理器宏定义 (name -> value)
|
||||
std::unordered_map<std::string, std::string> defines;
|
||||
|
||||
/// include 搜索路径
|
||||
std::vector<std::filesystem::path> include_paths;
|
||||
|
||||
/// 入口点名称(默认为 "main")
|
||||
std::string entry_point{"main"};
|
||||
|
||||
/// GLSL 版本(用于 GLSL 源码)
|
||||
u32 glsl_version{460};
|
||||
|
||||
/// 是否启用 Vulkan 语义
|
||||
bool vulkan_semantics{true};
|
||||
|
||||
/// 是否自动绑定资源
|
||||
bool auto_bind_resources{false};
|
||||
|
||||
/// 是否启用行列主序矩阵
|
||||
bool row_major_matrices{false};
|
||||
|
||||
/// 是否启用严格模式
|
||||
bool strict_mode{false};
|
||||
|
||||
/// 是否将警告视为错误
|
||||
bool warnings_as_errors{false};
|
||||
|
||||
/**
|
||||
* @brief 添加宏定义
|
||||
* @param name 宏名称
|
||||
* @param value 宏值(可选)
|
||||
* @return 自身引用,支持链式调用
|
||||
*/
|
||||
shader_compile_options& define(const std::string& name, const std::string& value = "1") {
|
||||
defines[name] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 添加 include 路径
|
||||
* @param path include 搜索路径
|
||||
* @return 自身引用,支持链式调用
|
||||
*/
|
||||
shader_compile_options& add_include_path(const std::filesystem::path& path) {
|
||||
include_paths.push_back(path);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置入口点
|
||||
* @param name 入口点名称
|
||||
* @return 自身引用,支持链式调用
|
||||
*/
|
||||
shader_compile_options& set_entry_point(const std::string& name) {
|
||||
entry_point = name;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启用调试模式
|
||||
* @return 自身引用,支持链式调用
|
||||
*/
|
||||
shader_compile_options& enable_debug() {
|
||||
generate_debug_info = true;
|
||||
optimization = shader_optimization_level::none;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 着色器编译结果
|
||||
*/
|
||||
struct shader_compile_result {
|
||||
/// 是否编译成功
|
||||
bool success{false};
|
||||
|
||||
/// SPIR-V 字节码
|
||||
std::vector<u32> spirv;
|
||||
|
||||
/// 反射信息
|
||||
shader_reflection reflection;
|
||||
|
||||
/// 编译诊断信息(错误和警告)
|
||||
std::vector<compile_diagnostic> diagnostics;
|
||||
|
||||
/// 依赖文件列表(包含的文件)
|
||||
std::vector<std::filesystem::path> dependencies;
|
||||
|
||||
/// 预处理后的源码(可选,用于调试)
|
||||
std::optional<std::string> preprocessed_source;
|
||||
|
||||
/**
|
||||
* @brief 检查是否有错误
|
||||
* @return 是否有错误
|
||||
*/
|
||||
[[nodiscard]] bool has_errors() const noexcept {
|
||||
return std::ranges::any_of(diagnostics, [](const auto& d) {
|
||||
return d.severity == diagnostic_severity::error;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否有警告
|
||||
* @return 是否有警告
|
||||
*/
|
||||
[[nodiscard]] bool has_warnings() const noexcept {
|
||||
return std::ranges::any_of(diagnostics, [](const auto& d) {
|
||||
return d.severity == diagnostic_severity::warning;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取所有错误
|
||||
* @return 错误列表
|
||||
*/
|
||||
[[nodiscard]] std::vector<compile_diagnostic> get_errors() const {
|
||||
std::vector<compile_diagnostic> errors;
|
||||
for (const auto& d : diagnostics) {
|
||||
if (d.severity == diagnostic_severity::error) {
|
||||
errors.push_back(d);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取所有警告
|
||||
* @return 警告列表
|
||||
*/
|
||||
[[nodiscard]] std::vector<compile_diagnostic> get_warnings() const {
|
||||
std::vector<compile_diagnostic> warnings;
|
||||
for (const auto& d : diagnostics) {
|
||||
if (d.severity == diagnostic_severity::warning) {
|
||||
warnings.push_back(d);
|
||||
}
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取格式化的诊断信息字符串
|
||||
* @return 诊断信息字符串
|
||||
*/
|
||||
[[nodiscard]] std::string get_diagnostics_string() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Include 文件解析器回调类型
|
||||
* @param include_name 要包含的文件名
|
||||
* @param requesting_source 请求包含的源文件路径
|
||||
* @param is_system_include 是否为系统 include(使用 <> 而非 "")
|
||||
* @return 包含文件的内容和完整路径,或空表示找不到
|
||||
*/
|
||||
using include_resolver = std::function<std::optional<std::pair<std::string, std::filesystem::path>>(
|
||||
std::string_view include_name,
|
||||
const std::filesystem::path& requesting_source,
|
||||
bool is_system_include
|
||||
)>;
|
||||
|
||||
/**
|
||||
* @brief 着色器编译器
|
||||
*
|
||||
* @details
|
||||
* 封装了 Slang/glslang 编译器,提供统一的着色器编译接口。
|
||||
* 支持 GLSL、HLSL、Slang 源码编译为 SPIR-V。
|
||||
*
|
||||
* 使用示例:
|
||||
* @code
|
||||
* shader_compiler compiler;
|
||||
*
|
||||
* // 设置 include 解析器
|
||||
* compiler.set_include_resolver([](auto name, auto path, auto sys) {
|
||||
* // 自定义 include 解析逻辑
|
||||
* return std::nullopt;
|
||||
* });
|
||||
*
|
||||
* // 编译 GLSL 源码
|
||||
* shader_compile_options options;
|
||||
* options.define("USE_NORMAL_MAP", "1");
|
||||
*
|
||||
* auto result = compiler.compile_from_source(
|
||||
* glsl_source,
|
||||
* shader_stage::vertex,
|
||||
* options
|
||||
* );
|
||||
*
|
||||
* if (result.success) {
|
||||
* // 使用 result.spirv 和 result.reflection
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
class shader_compiler {
|
||||
public:
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
shader_compiler();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~shader_compiler();
|
||||
|
||||
// 禁用拷贝
|
||||
shader_compiler(const shader_compiler&) = delete;
|
||||
shader_compiler& operator=(const shader_compiler&) = delete;
|
||||
|
||||
// 允许移动
|
||||
shader_compiler(shader_compiler&& other) noexcept;
|
||||
shader_compiler& operator=(shader_compiler&& other) noexcept;
|
||||
|
||||
/**
|
||||
* @brief 从源码编译着色器
|
||||
* @param source 着色器源码
|
||||
* @param stage 着色器阶段
|
||||
* @param options 编译选项
|
||||
* @return 编译结果
|
||||
*/
|
||||
[[nodiscard]] shader_compile_result compile_from_source(
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options = {}
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 从文件编译着色器
|
||||
* @param path 着色器文件路径
|
||||
* @param stage 着色器阶段
|
||||
* @param options 编译选项
|
||||
* @return 编译结果
|
||||
*/
|
||||
[[nodiscard]] shader_compile_result compile_from_file(
|
||||
const std::filesystem::path& path,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options = {}
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 编译 Slang 着色器
|
||||
* @param source Slang 源码
|
||||
* @param entry_point 入口点名称
|
||||
* @param stage 着色器阶段
|
||||
* @param options 编译选项
|
||||
* @return 编译结果
|
||||
*/
|
||||
[[nodiscard]] shader_compile_result compile_slang(
|
||||
std::string_view source,
|
||||
std::string_view entry_point,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options = {}
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 预处理着色器源码
|
||||
* @param source 原始源码
|
||||
* @param options 预处理选项
|
||||
* @return 预处理后的源码,失败返回空
|
||||
*/
|
||||
[[nodiscard]] std::optional<std::string> preprocess(
|
||||
std::string_view source,
|
||||
const shader_compile_options& options = {}
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 验证 SPIR-V 字节码
|
||||
* @param spirv SPIR-V 字节码
|
||||
* @return 验证结果,包含任何错误或警告
|
||||
*/
|
||||
[[nodiscard]] shader_compile_result validate_spirv(std::span<const u32> spirv);
|
||||
|
||||
/**
|
||||
* @brief 优化 SPIR-V 字节码
|
||||
* @param spirv 原始 SPIR-V 字节码
|
||||
* @param level 优化级别
|
||||
* @return 优化后的 SPIR-V,失败返回空
|
||||
*/
|
||||
[[nodiscard]] std::optional<std::vector<u32>> optimize_spirv(
|
||||
std::span<const u32> spirv,
|
||||
shader_optimization_level level = shader_optimization_level::performance
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 反汇编 SPIR-V 为可读文本
|
||||
* @param spirv SPIR-V 字节码
|
||||
* @return 反汇编文本,失败返回空
|
||||
*/
|
||||
[[nodiscard]] std::optional<std::string> disassemble_spirv(std::span<const u32> spirv);
|
||||
|
||||
/**
|
||||
* @brief 设置 include 解析器
|
||||
* @param resolver include 解析器回调
|
||||
*/
|
||||
void set_include_resolver(include_resolver resolver);
|
||||
|
||||
/**
|
||||
* @brief 获取默认 include 解析器
|
||||
* @param search_paths 搜索路径列表
|
||||
* @return 默认 include 解析器
|
||||
*/
|
||||
[[nodiscard]] static include_resolver make_default_include_resolver(
|
||||
const std::vector<std::filesystem::path>& search_paths
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 从文件扩展名推断着色器阶段
|
||||
* @param path 文件路径
|
||||
* @return 推断的着色器阶段,无法推断返回空
|
||||
*/
|
||||
[[nodiscard]] static std::optional<shader_stage> infer_stage_from_extension(
|
||||
const std::filesystem::path& path
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 从文件扩展名推断源语言
|
||||
* @param path 文件路径
|
||||
* @return 推断的源语言
|
||||
*/
|
||||
[[nodiscard]] static shader_source_language infer_language_from_extension(
|
||||
const std::filesystem::path& path
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 检查 Slang 编译器是否可用
|
||||
* @return Slang 是否可用
|
||||
*/
|
||||
[[nodiscard]] static bool is_slang_available() noexcept;
|
||||
|
||||
/**
|
||||
* @brief 获取支持的着色器文件扩展名列表
|
||||
* @return 扩展名列表
|
||||
*/
|
||||
[[nodiscard]] static std::vector<std::string> get_supported_extensions();
|
||||
|
||||
private:
|
||||
/// 前向声明实现类
|
||||
struct impl;
|
||||
std::unique_ptr<impl> impl_;
|
||||
|
||||
/// include 解析器
|
||||
include_resolver include_resolver_;
|
||||
|
||||
/**
|
||||
* @brief 内部编译函数
|
||||
*/
|
||||
[[nodiscard]] shader_compile_result compile_internal(
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const std::filesystem::path& source_path,
|
||||
const shader_compile_options& options
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 检测源语言
|
||||
*/
|
||||
[[nodiscard]] shader_source_language detect_language(
|
||||
std::string_view source,
|
||||
const std::filesystem::path& path
|
||||
) const;
|
||||
|
||||
/**
|
||||
* @brief 使用 glslang 编译 GLSL
|
||||
*/
|
||||
[[nodiscard]] shader_compile_result compile_glsl(
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const std::filesystem::path& source_path,
|
||||
const shader_compile_options& options
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 使用 Slang 编译
|
||||
*/
|
||||
[[nodiscard]] shader_compile_result compile_with_slang(
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const std::filesystem::path& source_path,
|
||||
const shader_compile_options& options
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 处理 include 指令
|
||||
*/
|
||||
[[nodiscard]] std::optional<std::string> resolve_includes(
|
||||
std::string_view source,
|
||||
const std::filesystem::path& source_path,
|
||||
const shader_compile_options& options,
|
||||
std::vector<std::filesystem::path>& dependencies,
|
||||
std::unordered_set<std::string>& included_files
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 着色器编译器工厂
|
||||
*
|
||||
* @details
|
||||
* 提供创建和管理着色器编译器实例的工厂方法。
|
||||
*/
|
||||
class shader_compiler_factory {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取全局编译器实例
|
||||
* @return 编译器引用
|
||||
*/
|
||||
[[nodiscard]] static shader_compiler& get_instance();
|
||||
|
||||
/**
|
||||
* @brief 创建新的编译器实例
|
||||
* @return 新编译器实例
|
||||
*/
|
||||
[[nodiscard]] static std::unique_ptr<shader_compiler> create();
|
||||
|
||||
// 禁止实例化
|
||||
shader_compiler_factory() = delete;
|
||||
};
|
||||
|
||||
} // namespace mirai
|
||||
@@ -1,529 +0,0 @@
|
||||
/**
|
||||
* @file shader_hot_reload.cpp
|
||||
* @brief MIRAI 框架着色器热重载系统实现
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*/
|
||||
|
||||
#include "shader_hot_reload.hpp"
|
||||
#include "shader_compiler.hpp"
|
||||
#include "shader_library.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <regex>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// ================================================================================================
|
||||
// shader_hot_reload 实现
|
||||
// ================================================================================================
|
||||
|
||||
shader_hot_reload::shader_hot_reload() : config_() {
|
||||
last_check_time_ = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
shader_hot_reload::shader_hot_reload(const hot_reload_config& config) : config_(config) {
|
||||
last_check_time_ = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
shader_hot_reload::~shader_hot_reload() {
|
||||
stop_background_thread();
|
||||
}
|
||||
|
||||
bool shader_hot_reload::watch(const std::filesystem::path& path, std::string_view shader_name) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (!std::filesystem::exists(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string normalized = normalize_path(path);
|
||||
|
||||
if (std::filesystem::is_directory(path)) {
|
||||
// 监视目录
|
||||
scan_directory(path);
|
||||
|
||||
watch_entry entry;
|
||||
entry.path = path;
|
||||
entry.is_directory = true;
|
||||
entry.last_modified = std::filesystem::last_write_time(path);
|
||||
|
||||
watch_entries_[normalized] = entry;
|
||||
} else {
|
||||
// 监视单个文件
|
||||
if (!matches_extension(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
watch_entry entry;
|
||||
entry.path = path;
|
||||
entry.is_directory = false;
|
||||
entry.last_modified = std::filesystem::last_write_time(path);
|
||||
entry.file_size = std::filesystem::file_size(path);
|
||||
|
||||
if (!shader_name.empty()) {
|
||||
entry.dependents.push_back(std::string(shader_name));
|
||||
shader_sources_[std::string(shader_name)] = path;
|
||||
}
|
||||
|
||||
watch_entries_[normalized] = entry;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool shader_hot_reload::unwatch(const std::filesystem::path& path) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
std::string normalized = normalize_path(path);
|
||||
auto it = watch_entries_.find(normalized);
|
||||
|
||||
if (it == watch_entries_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果是目录,移除所有子文件
|
||||
if (it->second.is_directory) {
|
||||
std::vector<std::string> to_remove;
|
||||
for (const auto& [key, entry] : watch_entries_) {
|
||||
if (!entry.is_directory && entry.path.string().starts_with(path.string())) {
|
||||
to_remove.push_back(key);
|
||||
}
|
||||
}
|
||||
for (const auto& key : to_remove) {
|
||||
watch_entries_.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
watch_entries_.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
void shader_hot_reload::unwatch_all() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
watch_entries_.clear();
|
||||
shader_sources_.clear();
|
||||
file_dependents_.clear();
|
||||
}
|
||||
|
||||
void shader_hot_reload::add_dependency(std::string_view shader_name, const std::filesystem::path& dependency_path) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
std::string normalized = normalize_path(dependency_path);
|
||||
file_dependents_[normalized].insert(std::string(shader_name));
|
||||
|
||||
// 如果文件还没被监视,添加监视
|
||||
if (watch_entries_.find(normalized) == watch_entries_.end()) {
|
||||
if (std::filesystem::exists(dependency_path)) {
|
||||
watch_entry entry;
|
||||
entry.path = dependency_path;
|
||||
entry.is_directory = false;
|
||||
entry.last_modified = std::filesystem::last_write_time(dependency_path);
|
||||
entry.file_size = std::filesystem::file_size(dependency_path);
|
||||
entry.dependents.push_back(std::string(shader_name));
|
||||
|
||||
watch_entries_[normalized] = entry;
|
||||
}
|
||||
} else {
|
||||
// 添加到依赖列表
|
||||
auto& dependents = watch_entries_[normalized].dependents;
|
||||
if (std::find(dependents.begin(), dependents.end(), shader_name) == dependents.end()) {
|
||||
dependents.push_back(std::string(shader_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void shader_hot_reload::remove_dependency(std::string_view shader_name, const std::filesystem::path& dependency_path) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
std::string normalized = normalize_path(dependency_path);
|
||||
|
||||
auto it = file_dependents_.find(normalized);
|
||||
if (it != file_dependents_.end()) {
|
||||
it->second.erase(std::string(shader_name));
|
||||
if (it->second.empty()) {
|
||||
file_dependents_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
auto entry_it = watch_entries_.find(normalized);
|
||||
if (entry_it != watch_entries_.end()) {
|
||||
auto& dependents = entry_it->second.dependents;
|
||||
dependents.erase(
|
||||
std::remove(dependents.begin(), dependents.end(), shader_name),
|
||||
dependents.end()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void shader_hot_reload::clear_dependencies(std::string_view shader_name) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
std::string name(shader_name);
|
||||
|
||||
for (auto& [path, dependents] : file_dependents_) {
|
||||
dependents.erase(name);
|
||||
}
|
||||
|
||||
for (auto& [path, entry] : watch_entries_) {
|
||||
auto& dependents = entry.dependents;
|
||||
dependents.erase(
|
||||
std::remove(dependents.begin(), dependents.end(), name),
|
||||
dependents.end()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> shader_hot_reload::get_dependencies(std::string_view shader_name) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
std::vector<std::filesystem::path> result;
|
||||
|
||||
for (const auto& [path, dependents] : file_dependents_) {
|
||||
if (dependents.find(std::string(shader_name)) != dependents.end()) {
|
||||
result.push_back(path);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void shader_hot_reload::on_shader_changed(shader_change_callback callback) {
|
||||
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||
shader_callbacks_.push_back(std::move(callback));
|
||||
}
|
||||
|
||||
void shader_hot_reload::on_file_changed(file_change_callback callback) {
|
||||
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||
file_callbacks_.push_back(std::move(callback));
|
||||
}
|
||||
|
||||
u32 shader_hot_reload::check_for_changes() {
|
||||
if (!enabled_.load()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
u32 change_count = 0;
|
||||
|
||||
std::vector<file_change_event> events;
|
||||
|
||||
for (auto& [path_str, entry] : watch_entries_) {
|
||||
if (entry.is_directory) {
|
||||
// 对于目录,检查新文件
|
||||
if (config_.recursive) {
|
||||
for (const auto& dir_entry : std::filesystem::recursive_directory_iterator(entry.path)) {
|
||||
if (dir_entry.is_regular_file() && matches_extension(dir_entry.path())) {
|
||||
std::string file_path = normalize_path(dir_entry.path());
|
||||
if (watch_entries_.find(file_path) == watch_entries_.end()) {
|
||||
// 新文件
|
||||
file_change_event event;
|
||||
event.path = dir_entry.path();
|
||||
event.type = file_change_type::created;
|
||||
event.timestamp = now;
|
||||
events.push_back(event);
|
||||
|
||||
// 添加到监视
|
||||
watch_entry new_entry;
|
||||
new_entry.path = dir_entry.path();
|
||||
new_entry.is_directory = false;
|
||||
new_entry.last_modified = std::filesystem::last_write_time(dir_entry.path());
|
||||
new_entry.file_size = std::filesystem::file_size(dir_entry.path());
|
||||
watch_entries_[file_path] = new_entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(entry.path)) {
|
||||
// 文件被删除
|
||||
file_change_event event;
|
||||
event.path = entry.path;
|
||||
event.type = file_change_type::deleted;
|
||||
event.timestamp = now;
|
||||
events.push_back(event);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto current_time = std::filesystem::last_write_time(entry.path);
|
||||
auto current_size = std::filesystem::file_size(entry.path);
|
||||
|
||||
if (current_time != entry.last_modified || current_size != entry.file_size) {
|
||||
// 文件被修改
|
||||
file_change_event event;
|
||||
event.path = entry.path;
|
||||
event.type = file_change_type::modified;
|
||||
event.timestamp = now;
|
||||
events.push_back(event);
|
||||
|
||||
entry.last_modified = current_time;
|
||||
entry.file_size = current_size;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理事件
|
||||
for (const auto& event : events) {
|
||||
process_file_change(event);
|
||||
++change_count;
|
||||
}
|
||||
|
||||
last_check_time_ = now;
|
||||
return change_count;
|
||||
}
|
||||
|
||||
void shader_hot_reload::enable() {
|
||||
enabled_.store(true);
|
||||
}
|
||||
|
||||
void shader_hot_reload::disable() {
|
||||
enabled_.store(false);
|
||||
}
|
||||
|
||||
void shader_hot_reload::start_background_thread() {
|
||||
if (background_thread_running_.load()) {
|
||||
return;
|
||||
}
|
||||
|
||||
should_stop_.store(false);
|
||||
background_thread_running_.store(true);
|
||||
|
||||
background_thread_ = std::make_unique<std::thread>(&shader_hot_reload::background_thread_func, this);
|
||||
}
|
||||
|
||||
void shader_hot_reload::stop_background_thread() {
|
||||
if (!background_thread_running_.load()) {
|
||||
return;
|
||||
}
|
||||
|
||||
should_stop_.store(true);
|
||||
|
||||
if (background_thread_ && background_thread_->joinable()) {
|
||||
background_thread_->join();
|
||||
}
|
||||
|
||||
background_thread_.reset();
|
||||
background_thread_running_.store(false);
|
||||
}
|
||||
|
||||
bool shader_hot_reload::force_reload(std::string_view shader_name) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto it = shader_sources_.find(std::string(shader_name));
|
||||
if (it == shader_sources_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
trigger_reload(shader_name, it->second);
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 shader_hot_reload::force_reload_all() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
u32 count = 0;
|
||||
for (const auto& [name, path] : shader_sources_) {
|
||||
trigger_reload(name, path);
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
u32 shader_hot_reload::get_watch_count() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return static_cast<u32>(watch_entries_.size());
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> shader_hot_reload::get_watched_paths() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
std::vector<std::filesystem::path> paths;
|
||||
paths.reserve(watch_entries_.size());
|
||||
|
||||
for (const auto& [path_str, entry] : watch_entries_) {
|
||||
paths.push_back(entry.path);
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
void shader_hot_reload::set_config(const hot_reload_config& config) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
config_ = config;
|
||||
}
|
||||
|
||||
bool shader_hot_reload::matches_extension(const std::filesystem::path& path) const {
|
||||
auto ext = path.extension().string();
|
||||
std::ranges::transform(ext, ext.begin(), ::tolower);
|
||||
|
||||
for (const auto& watch_ext : config_.watch_extensions) {
|
||||
if (ext == watch_ext) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool shader_hot_reload::should_ignore(const std::filesystem::path& path) const {
|
||||
std::string filename = path.filename().string();
|
||||
|
||||
for (const auto& pattern : config_.ignore_patterns) {
|
||||
// 简单的模式匹配
|
||||
if (pattern.starts_with("*")) {
|
||||
// 后缀匹配
|
||||
std::string suffix = pattern.substr(1);
|
||||
if (filename.ends_with(suffix)) {
|
||||
return true;
|
||||
}
|
||||
} else if (pattern.ends_with("*")) {
|
||||
// 前缀匹配
|
||||
std::string prefix = pattern.substr(0, pattern.size() - 1);
|
||||
if (filename.starts_with(prefix)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// 精确匹配
|
||||
if (filename == pattern) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void shader_hot_reload::background_thread_func() {
|
||||
while (!should_stop_.load()) {
|
||||
if (enabled_.load()) {
|
||||
check_for_changes();
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(config_.poll_interval_ms));
|
||||
}
|
||||
}
|
||||
|
||||
void shader_hot_reload::scan_directory(const std::filesystem::path& directory) {
|
||||
if (!std::filesystem::exists(directory) || !std::filesystem::is_directory(directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto iterator = config_.recursive
|
||||
? std::filesystem::recursive_directory_iterator(directory)
|
||||
: std::filesystem::recursive_directory_iterator(directory, std::filesystem::directory_options::none);
|
||||
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) {
|
||||
if (!entry.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!matches_extension(entry.path())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (should_ignore(entry.path())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string normalized = normalize_path(entry.path());
|
||||
|
||||
if (watch_entries_.find(normalized) == watch_entries_.end()) {
|
||||
watch_entry new_entry;
|
||||
new_entry.path = entry.path();
|
||||
new_entry.is_directory = false;
|
||||
new_entry.last_modified = std::filesystem::last_write_time(entry.path());
|
||||
new_entry.file_size = std::filesystem::file_size(entry.path());
|
||||
|
||||
watch_entries_[normalized] = new_entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void shader_hot_reload::process_file_change(const file_change_event& event) {
|
||||
// 通知文件变化
|
||||
notify_file_change(event);
|
||||
|
||||
if (!config_.auto_recompile) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string normalized = normalize_path(event.path);
|
||||
|
||||
// 查找依赖此文件的着色器
|
||||
std::unordered_set<std::string> affected_shaders;
|
||||
|
||||
auto entry_it = watch_entries_.find(normalized);
|
||||
if (entry_it != watch_entries_.end()) {
|
||||
for (const auto& dep : entry_it->second.dependents) {
|
||||
affected_shaders.insert(dep);
|
||||
}
|
||||
}
|
||||
|
||||
auto dep_it = file_dependents_.find(normalized);
|
||||
if (dep_it != file_dependents_.end()) {
|
||||
affected_shaders.insert(dep_it->second.begin(), dep_it->second.end());
|
||||
}
|
||||
|
||||
// 触发重载
|
||||
for (const auto& shader_name : affected_shaders) {
|
||||
auto source_it = shader_sources_.find(shader_name);
|
||||
if (source_it != shader_sources_.end()) {
|
||||
trigger_reload(shader_name, source_it->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void shader_hot_reload::trigger_reload(std::string_view shader_name, const std::filesystem::path& source_path) {
|
||||
shader_reload_event event;
|
||||
event.name = std::string(shader_name);
|
||||
event.source_path = source_path;
|
||||
event.timestamp = std::chrono::system_clock::now();
|
||||
|
||||
// 尝试重新编译
|
||||
if (compiler_ != nullptr) {
|
||||
auto stage = shader_compiler::infer_stage_from_extension(source_path);
|
||||
if (stage) {
|
||||
auto result = compiler_->compile_from_file(source_path, *stage);
|
||||
event.success = result.success;
|
||||
if (!result.success) {
|
||||
event.error_message = result.get_diagnostics_string();
|
||||
}
|
||||
} else {
|
||||
event.success = false;
|
||||
event.error_message = "Could not infer shader stage from file extension";
|
||||
}
|
||||
} else {
|
||||
event.success = false;
|
||||
event.error_message = "No compiler available";
|
||||
}
|
||||
|
||||
notify_shader_reload(event);
|
||||
}
|
||||
|
||||
void shader_hot_reload::notify_file_change(const file_change_event& event) {
|
||||
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||
for (const auto& callback : file_callbacks_) {
|
||||
if (callback) {
|
||||
callback(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void shader_hot_reload::notify_shader_reload(const shader_reload_event& event) {
|
||||
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||
for (const auto& callback : shader_callbacks_) {
|
||||
if (callback) {
|
||||
callback(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string shader_hot_reload::normalize_path(const std::filesystem::path& path) {
|
||||
return std::filesystem::weakly_canonical(path).string();
|
||||
}
|
||||
|
||||
} // namespace mirai
|
||||
@@ -1,463 +0,0 @@
|
||||
/**
|
||||
* @file shader_hot_reload.hpp
|
||||
* @brief MIRAI 框架着色器热重载系统
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* @details
|
||||
* 本文件实现了着色器热重载系统,提供:
|
||||
* - 文件系统监视
|
||||
* - 自动重新编译变化的着色器
|
||||
* - 依赖追踪(include 文件)
|
||||
* - 回调通知机制
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shader_types.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// 前向声明
|
||||
class shader_compiler;
|
||||
class shader_module;
|
||||
class shader_library;
|
||||
|
||||
/**
|
||||
* @brief 文件变化类型
|
||||
*/
|
||||
enum class file_change_type : u8 {
|
||||
created, ///< 文件创建
|
||||
modified, ///< 文件修改
|
||||
deleted ///< 文件删除
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 文件变化事件
|
||||
*/
|
||||
struct file_change_event {
|
||||
/// 文件路径
|
||||
std::filesystem::path path;
|
||||
|
||||
/// 变化类型
|
||||
file_change_type type{file_change_type::modified};
|
||||
|
||||
/// 变化时间
|
||||
std::chrono::system_clock::time_point timestamp;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 着色器重载事件
|
||||
*/
|
||||
struct shader_reload_event {
|
||||
/// 着色器名称
|
||||
std::string name;
|
||||
|
||||
/// 源文件路径
|
||||
std::filesystem::path source_path;
|
||||
|
||||
/// 是否重载成功
|
||||
bool success{false};
|
||||
|
||||
/// 错误信息(如果失败)
|
||||
std::string error_message;
|
||||
|
||||
/// 新的着色器模块(如果成功)
|
||||
shader_module* new_module{nullptr};
|
||||
|
||||
/// 重载时间
|
||||
std::chrono::system_clock::time_point timestamp;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 着色器变化回调类型
|
||||
*/
|
||||
using shader_change_callback = std::function<void(const shader_reload_event&)>;
|
||||
|
||||
/**
|
||||
* @brief 文件变化回调类型
|
||||
*/
|
||||
using file_change_callback = std::function<void(const file_change_event&)>;
|
||||
|
||||
/**
|
||||
* @brief 热重载配置
|
||||
*/
|
||||
struct hot_reload_config {
|
||||
/// 是否启用热重载
|
||||
bool enabled{true};
|
||||
|
||||
/// 轮询间隔(毫秒)
|
||||
u32 poll_interval_ms{100};
|
||||
|
||||
/// 防抖时间(毫秒)- 文件变化后等待此时间再触发
|
||||
u32 debounce_ms{200};
|
||||
|
||||
/// 是否自动重新编译
|
||||
bool auto_recompile{true};
|
||||
|
||||
/// 是否监视 include 文件
|
||||
bool watch_includes{true};
|
||||
|
||||
/// 是否递归监视目录
|
||||
bool recursive{true};
|
||||
|
||||
/// 要监视的文件扩展名
|
||||
std::vector<std::string> watch_extensions{".vert", ".frag", ".comp", ".geom", ".tesc", ".tese", ".glsl", ".hlsl", ".slang"};
|
||||
|
||||
/// 要忽略的文件/目录模式
|
||||
std::vector<std::string> ignore_patterns{".*", "_*", "*.bak", "*.tmp"};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 监视条目信息
|
||||
*/
|
||||
struct watch_entry {
|
||||
/// 文件路径
|
||||
std::filesystem::path path;
|
||||
|
||||
/// 最后修改时间
|
||||
std::filesystem::file_time_type last_modified;
|
||||
|
||||
/// 文件大小
|
||||
uintmax_t file_size{0};
|
||||
|
||||
/// 是否是目录
|
||||
bool is_directory{false};
|
||||
|
||||
/// 依赖此文件的着色器列表
|
||||
std::vector<std::string> dependents;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 着色器热重载管理器
|
||||
*
|
||||
* @details
|
||||
* 监视着色器文件变化并自动触发重新编译。
|
||||
*
|
||||
* 使用示例:
|
||||
* @code
|
||||
* hot_reload_config config;
|
||||
* config.poll_interval_ms = 100;
|
||||
*
|
||||
* shader_hot_reload hot_reload(config);
|
||||
*
|
||||
* // 注册变化回调
|
||||
* hot_reload.on_shader_changed([](const shader_reload_event& event) {
|
||||
* if (event.success) {
|
||||
* std::cout << "Shader reloaded: " << event.name << std::endl;
|
||||
* } else {
|
||||
* std::cerr << "Reload failed: " << event.error_message << std::endl;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // 监视目录
|
||||
* hot_reload.watch("shaders/");
|
||||
*
|
||||
* // 启动监视
|
||||
* hot_reload.enable();
|
||||
*
|
||||
* // 在主循环中调用
|
||||
* while (running) {
|
||||
* hot_reload.check_for_changes();
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* // 停止监视
|
||||
* hot_reload.disable();
|
||||
* @endcode
|
||||
*/
|
||||
class shader_hot_reload {
|
||||
public:
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
shader_hot_reload();
|
||||
|
||||
/**
|
||||
* @brief 带配置的构造函数
|
||||
* @param config 热重载配置
|
||||
*/
|
||||
explicit shader_hot_reload(const hot_reload_config& config);
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~shader_hot_reload();
|
||||
|
||||
// 禁用拷贝
|
||||
shader_hot_reload(const shader_hot_reload&) = delete;
|
||||
shader_hot_reload& operator=(const shader_hot_reload&) = delete;
|
||||
|
||||
// 禁用移动(包含线程)
|
||||
shader_hot_reload(shader_hot_reload&&) = delete;
|
||||
shader_hot_reload& operator=(shader_hot_reload&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 监视文件或目录
|
||||
* @param path 文件或目录路径
|
||||
* @param shader_name 关联的着色器名称(可选)
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool watch(const std::filesystem::path& path, std::string_view shader_name = "");
|
||||
|
||||
/**
|
||||
* @brief 取消监视
|
||||
* @param path 文件或目录路径
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool unwatch(const std::filesystem::path& path);
|
||||
|
||||
/**
|
||||
* @brief 取消所有监视
|
||||
*/
|
||||
void unwatch_all();
|
||||
|
||||
/**
|
||||
* @brief 添加依赖关系
|
||||
* @param shader_name 着色器名称
|
||||
* @param dependency_path 依赖文件路径
|
||||
*/
|
||||
void add_dependency(std::string_view shader_name, const std::filesystem::path& dependency_path);
|
||||
|
||||
/**
|
||||
* @brief 移除依赖关系
|
||||
* @param shader_name 着色器名称
|
||||
* @param dependency_path 依赖文件路径
|
||||
*/
|
||||
void remove_dependency(std::string_view shader_name, const std::filesystem::path& dependency_path);
|
||||
|
||||
/**
|
||||
* @brief 清除着色器的所有依赖
|
||||
* @param shader_name 着色器名称
|
||||
*/
|
||||
void clear_dependencies(std::string_view shader_name);
|
||||
|
||||
/**
|
||||
* @brief 获取着色器的依赖列表
|
||||
* @param shader_name 着色器名称
|
||||
* @return 依赖文件路径列表
|
||||
*/
|
||||
[[nodiscard]] std::vector<std::filesystem::path> get_dependencies(std::string_view shader_name) const;
|
||||
|
||||
/**
|
||||
* @brief 设置着色器变化回调
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
void on_shader_changed(shader_change_callback callback);
|
||||
|
||||
/**
|
||||
* @brief 设置文件变化回调
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
void on_file_changed(file_change_callback callback);
|
||||
|
||||
/**
|
||||
* @brief 检查文件变化
|
||||
* @return 变化的文件数量
|
||||
*/
|
||||
u32 check_for_changes();
|
||||
|
||||
/**
|
||||
* @brief 启用热重载
|
||||
*/
|
||||
void enable();
|
||||
|
||||
/**
|
||||
* @brief 禁用热重载
|
||||
*/
|
||||
void disable();
|
||||
|
||||
/**
|
||||
* @brief 检查是否启用
|
||||
* @return 是否启用
|
||||
*/
|
||||
[[nodiscard]] bool is_enabled() const noexcept { return enabled_.load(); }
|
||||
|
||||
/**
|
||||
* @brief 启动后台监视线程
|
||||
*/
|
||||
void start_background_thread();
|
||||
|
||||
/**
|
||||
* @brief 停止后台监视线程
|
||||
*/
|
||||
void stop_background_thread();
|
||||
|
||||
/**
|
||||
* @brief 检查后台线程是否运行
|
||||
* @return 是否运行
|
||||
*/
|
||||
[[nodiscard]] bool is_background_thread_running() const noexcept {
|
||||
return background_thread_running_.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 强制重载指定着色器
|
||||
* @param shader_name 着色器名称
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool force_reload(std::string_view shader_name);
|
||||
|
||||
/**
|
||||
* @brief 强制重载所有着色器
|
||||
* @return 重载的着色器数量
|
||||
*/
|
||||
u32 force_reload_all();
|
||||
|
||||
/**
|
||||
* @brief 获取监视的文件数量
|
||||
* @return 文件数量
|
||||
*/
|
||||
[[nodiscard]] u32 get_watch_count() const;
|
||||
|
||||
/**
|
||||
* @brief 获取所有监视的路径
|
||||
* @return 路径列表
|
||||
*/
|
||||
[[nodiscard]] std::vector<std::filesystem::path> get_watched_paths() const;
|
||||
|
||||
/**
|
||||
* @brief 获取配置
|
||||
* @return 配置引用
|
||||
*/
|
||||
[[nodiscard]] const hot_reload_config& get_config() const noexcept { return config_; }
|
||||
|
||||
/**
|
||||
* @brief 设置配置
|
||||
* @param config 新配置
|
||||
*/
|
||||
void set_config(const hot_reload_config& config);
|
||||
|
||||
/**
|
||||
* @brief 设置着色器编译器
|
||||
* @param compiler 编译器指针
|
||||
*/
|
||||
void set_compiler(shader_compiler* compiler) { compiler_ = compiler; }
|
||||
|
||||
/**
|
||||
* @brief 设置着色器库
|
||||
* @param library 库指针
|
||||
*/
|
||||
void set_library(shader_library* library) { library_ = library; }
|
||||
|
||||
/**
|
||||
* @brief 检查文件是否匹配监视扩展名
|
||||
* @param path 文件路径
|
||||
* @return 是否匹配
|
||||
*/
|
||||
[[nodiscard]] bool matches_extension(const std::filesystem::path& path) const;
|
||||
|
||||
/**
|
||||
* @brief 检查路径是否应该被忽略
|
||||
* @param path 路径
|
||||
* @return 是否忽略
|
||||
*/
|
||||
[[nodiscard]] bool should_ignore(const std::filesystem::path& path) const;
|
||||
|
||||
private:
|
||||
/// 配置
|
||||
hot_reload_config config_;
|
||||
|
||||
/// 监视条目
|
||||
std::unordered_map<std::string, watch_entry> watch_entries_;
|
||||
|
||||
/// 着色器名称到源文件的映射
|
||||
std::unordered_map<std::string, std::filesystem::path> shader_sources_;
|
||||
|
||||
/// 文件到依赖它的着色器的映射
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> file_dependents_;
|
||||
|
||||
/// 着色器变化回调列表
|
||||
std::vector<shader_change_callback> shader_callbacks_;
|
||||
|
||||
/// 文件变化回调列表
|
||||
std::vector<file_change_callback> file_callbacks_;
|
||||
|
||||
/// 待处理的变化事件
|
||||
std::vector<file_change_event> pending_events_;
|
||||
|
||||
/// 上次检查时间
|
||||
std::chrono::system_clock::time_point last_check_time_;
|
||||
|
||||
/// 编译器指针
|
||||
shader_compiler* compiler_{nullptr};
|
||||
|
||||
/// 着色器库指针
|
||||
shader_library* library_{nullptr};
|
||||
|
||||
/// 是否启用
|
||||
std::atomic<bool> enabled_{false};
|
||||
|
||||
/// 后台线程是否运行
|
||||
std::atomic<bool> background_thread_running_{false};
|
||||
|
||||
/// 是否应该停止
|
||||
std::atomic<bool> should_stop_{false};
|
||||
|
||||
/// 后台线程
|
||||
std::unique_ptr<std::thread> background_thread_;
|
||||
|
||||
/// 互斥锁
|
||||
mutable std::mutex mutex_;
|
||||
|
||||
/// 回调互斥锁
|
||||
mutable std::mutex callback_mutex_;
|
||||
|
||||
/**
|
||||
* @brief 后台线程函数
|
||||
*/
|
||||
void background_thread_func();
|
||||
|
||||
/**
|
||||
* @brief 扫描目录
|
||||
* @param directory 目录路径
|
||||
*/
|
||||
void scan_directory(const std::filesystem::path& directory);
|
||||
|
||||
/**
|
||||
* @brief 处理文件变化
|
||||
* @param event 变化事件
|
||||
*/
|
||||
void process_file_change(const file_change_event& event);
|
||||
|
||||
/**
|
||||
* @brief 触发着色器重载
|
||||
* @param shader_name 着色器名称
|
||||
* @param source_path 源文件路径
|
||||
*/
|
||||
void trigger_reload(std::string_view shader_name, const std::filesystem::path& source_path);
|
||||
|
||||
/**
|
||||
* @brief 通知文件变化
|
||||
* @param event 变化事件
|
||||
*/
|
||||
void notify_file_change(const file_change_event& event);
|
||||
|
||||
/**
|
||||
* @brief 通知着色器重载
|
||||
* @param event 重载事件
|
||||
*/
|
||||
void notify_shader_reload(const shader_reload_event& event);
|
||||
|
||||
/**
|
||||
* @brief 规范化路径
|
||||
* @param path 原始路径
|
||||
* @return 规范化后的路径
|
||||
*/
|
||||
[[nodiscard]] static std::string normalize_path(const std::filesystem::path& path);
|
||||
};
|
||||
|
||||
} // namespace mirai
|
||||
@@ -15,129 +15,33 @@ namespace mirai {
|
||||
// shader_library 实现
|
||||
// ================================================================================================
|
||||
|
||||
shader_library::shader_library(VkDevice device) : device_(device), config_() {
|
||||
// 设置默认 include 解析器
|
||||
compiler_.set_include_resolver(
|
||||
shader_compiler::make_default_include_resolver(config_.search_paths)
|
||||
);
|
||||
}
|
||||
|
||||
shader_library::shader_library(VkDevice device, const shader_library_config& config)
|
||||
: device_(device), config_(config) {
|
||||
|
||||
// 设置 include 解析器
|
||||
compiler_.set_include_resolver(
|
||||
shader_compiler::make_default_include_resolver(config_.search_paths)
|
||||
);
|
||||
|
||||
// 初始化缓存
|
||||
if (config_.enable_cache) {
|
||||
cache_ = std::make_unique<shader_cache>(config_.cache_config);
|
||||
}
|
||||
|
||||
// 初始化热重载
|
||||
if (config_.enable_hot_reload) {
|
||||
hot_reload_ = std::make_unique<shader_hot_reload>(config_.hot_reload_config);
|
||||
hot_reload_->set_compiler(&compiler_);
|
||||
hot_reload_->set_library(this);
|
||||
|
||||
// 注册热重载回调
|
||||
hot_reload_->on_shader_changed([this](const shader_reload_event& event) {
|
||||
on_hot_reload_event(event);
|
||||
});
|
||||
|
||||
// 监视搜索路径
|
||||
for (const auto& path : config_.search_paths) {
|
||||
hot_reload_->watch(path);
|
||||
}
|
||||
|
||||
hot_reload_->enable();
|
||||
}
|
||||
}
|
||||
shader_library::shader_library(VkDevice device) : device_(device) {}
|
||||
|
||||
shader_library::~shader_library() {
|
||||
if (hot_reload_) {
|
||||
hot_reload_->disable();
|
||||
hot_reload_->stop_background_thread();
|
||||
}
|
||||
|
||||
unload_all();
|
||||
}
|
||||
|
||||
shader_load_result shader_library::load(
|
||||
shader_load_result shader_library::load_spirv_data(
|
||||
std::string_view name,
|
||||
const std::filesystem::path& path,
|
||||
const shader_compile_options& options) {
|
||||
|
||||
// 尝试推断着色器阶段
|
||||
auto stage = shader_compiler::infer_stage_from_extension(path);
|
||||
if (!stage) {
|
||||
shader_load_result result;
|
||||
result.success = false;
|
||||
result.error_message = "Could not infer shader stage from file extension: " + path.string();
|
||||
return result;
|
||||
}
|
||||
|
||||
return load(name, path, *stage, options);
|
||||
}
|
||||
|
||||
shader_load_result shader_library::load(
|
||||
std::string_view name,
|
||||
const std::filesystem::path& path,
|
||||
std::span<const u32> spirv,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options) {
|
||||
|
||||
// 查找文件
|
||||
auto full_path = find_file(path);
|
||||
if (!full_path) {
|
||||
shader_load_result result;
|
||||
result.success = false;
|
||||
result.error_message = "File not found: " + path.string();
|
||||
return result;
|
||||
}
|
||||
|
||||
return load_internal(name, *full_path, stage, options);
|
||||
}
|
||||
|
||||
shader_load_result shader_library::load_from_source(
|
||||
std::string_view name,
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options) {
|
||||
std::string_view entry_point) {
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
shader_load_result result;
|
||||
|
||||
// 合并编译选项
|
||||
shader_compile_options merged_options = config_.default_compile_options;
|
||||
merged_options.source_language = options.source_language;
|
||||
merged_options.optimization = options.optimization;
|
||||
merged_options.generate_debug_info = options.generate_debug_info;
|
||||
merged_options.entry_point = options.entry_point;
|
||||
for (const auto& [key, value] : options.defines) {
|
||||
merged_options.defines[key] = value;
|
||||
}
|
||||
|
||||
// 编译
|
||||
shader_compile_result compile_result;
|
||||
if (cache_) {
|
||||
compile_result = cache_->get_or_compile(source, stage, merged_options, compiler_);
|
||||
} else {
|
||||
compile_result = compiler_.compile_from_source(source, stage, merged_options);
|
||||
}
|
||||
|
||||
if (!compile_result.success) {
|
||||
if (spirv.empty()) {
|
||||
result.success = false;
|
||||
result.error_message = compile_result.get_diagnostics_string();
|
||||
result.error_message = "Empty SPIR-V data";
|
||||
return result;
|
||||
}
|
||||
|
||||
// 创建模块
|
||||
shader_module_create_info create_info;
|
||||
create_info.stage = stage;
|
||||
create_info.spirv = std::move(compile_result.spirv);
|
||||
create_info.entry_point = merged_options.entry_point;
|
||||
create_info.spirv = std::vector<u32>(spirv.begin(), spirv.end());
|
||||
create_info.entry_point = std::string(entry_point);
|
||||
create_info.name = std::string(name);
|
||||
|
||||
auto module = shader_module::create(device_, create_info);
|
||||
@@ -156,77 +60,11 @@ shader_load_result shader_library::load_from_source(
|
||||
return result;
|
||||
}
|
||||
|
||||
program_load_result shader_library::load_program(
|
||||
shader_load_result shader_library::load_binary(
|
||||
std::string_view name,
|
||||
const std::filesystem::path& vertex_path,
|
||||
const std::filesystem::path& fragment_path,
|
||||
const shader_compile_options& options) {
|
||||
const shader_binary& binary) {
|
||||
|
||||
program_load_result result;
|
||||
|
||||
// 生成着色器名称
|
||||
std::string vs_name = std::string(name) + "_vs";
|
||||
std::string fs_name = std::string(name) + "_fs";
|
||||
|
||||
// 加载顶点着色器
|
||||
auto vs_result = load(vs_name, vertex_path, shader_stage::vertex, options);
|
||||
if (!vs_result.success) {
|
||||
result.success = false;
|
||||
result.error_message = "Failed to load vertex shader: " + vs_result.error_message;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 加载片段着色器
|
||||
auto fs_result = load(fs_name, fragment_path, shader_stage::fragment, options);
|
||||
if (!fs_result.success) {
|
||||
result.success = false;
|
||||
result.error_message = "Failed to load fragment shader: " + fs_result.error_message;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 创建程序
|
||||
return create_program(name, vs_name, fs_name);
|
||||
}
|
||||
|
||||
program_load_result shader_library::load_compute_program(
|
||||
std::string_view name,
|
||||
const std::filesystem::path& compute_path,
|
||||
const shader_compile_options& options) {
|
||||
|
||||
program_load_result result;
|
||||
|
||||
std::string cs_name = std::string(name) + "_cs";
|
||||
|
||||
// 加载计算着色器
|
||||
auto cs_result = load(cs_name, compute_path, shader_stage::compute, options);
|
||||
if (!cs_result.success) {
|
||||
result.success = false;
|
||||
result.error_message = "Failed to load compute shader: " + cs_result.error_message;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// 创建计算程序
|
||||
auto program = shader_program::create_compute(device_, modules_[cs_name].get(), name);
|
||||
if (!program || !program->is_valid()) {
|
||||
result.success = false;
|
||||
result.error_message = "Failed to create compute program";
|
||||
return result;
|
||||
}
|
||||
|
||||
// 保存程序信息
|
||||
std::string name_str(name);
|
||||
program_info info;
|
||||
info.compute_shader = cs_name;
|
||||
info.compute_path = compute_path;
|
||||
program_sources_[name_str] = info;
|
||||
|
||||
result.program = program.get();
|
||||
programs_[name_str] = std::move(program);
|
||||
|
||||
result.success = true;
|
||||
return result;
|
||||
return load_spirv_data(name, binary.spirv, binary.stage, binary.entry_point);
|
||||
}
|
||||
|
||||
program_load_result shader_library::create_program(
|
||||
@@ -238,21 +76,25 @@ program_load_result shader_library::create_program(
|
||||
|
||||
program_load_result result;
|
||||
|
||||
auto* vs = get(vertex_name);
|
||||
auto* fs = get(fragment_name);
|
||||
|
||||
if (!vs) {
|
||||
// 查找着色器模块
|
||||
auto vs_it = modules_.find(std::string(vertex_name));
|
||||
if (vs_it == modules_.end()) {
|
||||
result.success = false;
|
||||
result.error_message = "Vertex shader not found: " + std::string(vertex_name);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!fs) {
|
||||
auto fs_it = modules_.find(std::string(fragment_name));
|
||||
if (fs_it == modules_.end()) {
|
||||
result.success = false;
|
||||
result.error_message = "Fragment shader not found: " + std::string(fragment_name);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto* vs = vs_it->second.get();
|
||||
auto* fs = fs_it->second.get();
|
||||
|
||||
// 创建程序
|
||||
auto program = shader_program::create_graphics(device_, vs, fs, name);
|
||||
if (!program || !program->is_valid()) {
|
||||
result.success = false;
|
||||
@@ -264,24 +106,43 @@ program_load_result shader_library::create_program(
|
||||
return result;
|
||||
}
|
||||
|
||||
// 保存程序信息
|
||||
// 存储
|
||||
std::string name_str(name);
|
||||
program_info info;
|
||||
info.vertex_shader = std::string(vertex_name);
|
||||
info.fragment_shader = std::string(fragment_name);
|
||||
result.program = program.get();
|
||||
programs_[name_str] = std::move(program);
|
||||
|
||||
auto vs_it = module_sources_.find(std::string(vertex_name));
|
||||
if (vs_it != module_sources_.end()) {
|
||||
info.vertex_path = vs_it->second;
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
program_load_result shader_library::create_compute_program(
|
||||
std::string_view name,
|
||||
std::string_view compute_name) {
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
program_load_result result;
|
||||
|
||||
// 查找计算着色器模块
|
||||
auto cs_it = modules_.find(std::string(compute_name));
|
||||
if (cs_it == modules_.end()) {
|
||||
result.success = false;
|
||||
result.error_message = "Compute shader not found: " + std::string(compute_name);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto fs_it = module_sources_.find(std::string(fragment_name));
|
||||
if (fs_it != module_sources_.end()) {
|
||||
info.fragment_path = fs_it->second;
|
||||
auto* cs = cs_it->second.get();
|
||||
|
||||
// 创建程序
|
||||
auto program = shader_program::create_compute(device_, cs, name);
|
||||
if (!program || !program->is_valid()) {
|
||||
result.success = false;
|
||||
result.error_message = "Failed to create compute program";
|
||||
return result;
|
||||
}
|
||||
|
||||
program_sources_[name_str] = info;
|
||||
|
||||
// 存储
|
||||
std::string name_str(name);
|
||||
result.program = program.get();
|
||||
programs_[name_str] = std::move(program);
|
||||
|
||||
@@ -330,12 +191,6 @@ bool shader_library::unload(std::string_view name) {
|
||||
}
|
||||
|
||||
modules_.erase(it);
|
||||
module_sources_.erase(name_str);
|
||||
|
||||
if (hot_reload_) {
|
||||
hot_reload_->clear_dependencies(name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -350,8 +205,6 @@ bool shader_library::unload_program(std::string_view name) {
|
||||
}
|
||||
|
||||
programs_.erase(it);
|
||||
program_sources_.erase(name_str);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -360,132 +213,6 @@ void shader_library::unload_all() {
|
||||
|
||||
programs_.clear();
|
||||
modules_.clear();
|
||||
module_sources_.clear();
|
||||
program_sources_.clear();
|
||||
}
|
||||
|
||||
shader_load_result shader_library::reload(std::string_view name) {
|
||||
std::string name_str(name);
|
||||
|
||||
std::filesystem::path source_path;
|
||||
shader_stage stage;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto source_it = module_sources_.find(name_str);
|
||||
if (source_it == module_sources_.end()) {
|
||||
shader_load_result result;
|
||||
result.success = false;
|
||||
result.error_message = "Shader not found or has no source path: " + name_str;
|
||||
return result;
|
||||
}
|
||||
source_path = source_it->second;
|
||||
|
||||
auto module_it = modules_.find(name_str);
|
||||
if (module_it != modules_.end()) {
|
||||
stage = module_it->second->get_stage();
|
||||
} else {
|
||||
auto inferred = shader_compiler::infer_stage_from_extension(source_path);
|
||||
if (!inferred) {
|
||||
shader_load_result result;
|
||||
result.success = false;
|
||||
result.error_message = "Could not infer shader stage";
|
||||
return result;
|
||||
}
|
||||
stage = *inferred;
|
||||
}
|
||||
|
||||
// 先卸载
|
||||
modules_.erase(name_str);
|
||||
}
|
||||
|
||||
// 重新加载
|
||||
auto result = load_internal(name, source_path, stage, config_.default_compile_options);
|
||||
|
||||
if (result.success) {
|
||||
notify_shader_reload(name_str, result.module);
|
||||
rebuild_dependent_programs(name_str);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
program_load_result shader_library::reload_program(std::string_view name) {
|
||||
std::string name_str(name);
|
||||
|
||||
program_info info;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto it = program_sources_.find(name_str);
|
||||
if (it == program_sources_.end()) {
|
||||
program_load_result result;
|
||||
result.success = false;
|
||||
result.error_message = "Program not found: " + name_str;
|
||||
return result;
|
||||
}
|
||||
info = it->second;
|
||||
|
||||
// 先卸载
|
||||
programs_.erase(name_str);
|
||||
}
|
||||
|
||||
program_load_result result;
|
||||
|
||||
if (!info.compute_shader.empty()) {
|
||||
// 重新加载计算程序
|
||||
result = load_compute_program(name, info.compute_path);
|
||||
} else {
|
||||
// 重新加载图形程序
|
||||
result = load_program(name, info.vertex_path, info.fragment_path);
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
notify_program_reload(name_str, result.program);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
u32 shader_library::reload_all() {
|
||||
std::vector<std::string> shader_names;
|
||||
std::vector<std::string> program_names;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
for (const auto& [name, path] : module_sources_) {
|
||||
shader_names.push_back(name);
|
||||
}
|
||||
for (const auto& [name, info] : program_sources_) {
|
||||
program_names.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
u32 count = 0;
|
||||
|
||||
for (const auto& name : shader_names) {
|
||||
auto result = reload(name);
|
||||
if (result.success) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& name : program_names) {
|
||||
auto result = reload_program(name);
|
||||
if (result.success) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void shader_library::update() {
|
||||
if (hot_reload_ && hot_reload_->is_enabled()) {
|
||||
hot_reload_->check_for_changes();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> shader_library::get_shader_names() const {
|
||||
@@ -524,216 +251,4 @@ u32 shader_library::get_program_count() const {
|
||||
return static_cast<u32>(programs_.size());
|
||||
}
|
||||
|
||||
void shader_library::on_shader_reload(shader_reload_callback callback) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
shader_reload_callbacks_.push_back(std::move(callback));
|
||||
}
|
||||
|
||||
void shader_library::on_program_reload(program_reload_callback callback) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
program_reload_callbacks_.push_back(std::move(callback));
|
||||
}
|
||||
|
||||
void shader_library::add_search_path(const std::filesystem::path& path) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
config_.search_paths.push_back(path);
|
||||
|
||||
// 更新 include 解析器
|
||||
compiler_.set_include_resolver(
|
||||
shader_compiler::make_default_include_resolver(config_.search_paths)
|
||||
);
|
||||
|
||||
// 添加热重载监视
|
||||
if (hot_reload_) {
|
||||
hot_reload_->watch(path);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> shader_library::find_file(const std::filesystem::path& filename) const {
|
||||
// 先检查是否是绝对路径
|
||||
if (filename.is_absolute() && std::filesystem::exists(filename)) {
|
||||
return filename;
|
||||
}
|
||||
|
||||
// 在搜索路径中查找
|
||||
for (const auto& search_path : config_.search_paths) {
|
||||
auto full_path = search_path / filename;
|
||||
if (std::filesystem::exists(full_path)) {
|
||||
return full_path;
|
||||
}
|
||||
}
|
||||
|
||||
// 相对于当前目录
|
||||
if (std::filesystem::exists(filename)) {
|
||||
return filename;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void shader_library::enable_hot_reload() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (!hot_reload_) {
|
||||
hot_reload_ = std::make_unique<shader_hot_reload>(config_.hot_reload_config);
|
||||
hot_reload_->set_compiler(&compiler_);
|
||||
hot_reload_->set_library(this);
|
||||
|
||||
hot_reload_->on_shader_changed([this](const shader_reload_event& event) {
|
||||
on_hot_reload_event(event);
|
||||
});
|
||||
|
||||
for (const auto& path : config_.search_paths) {
|
||||
hot_reload_->watch(path);
|
||||
}
|
||||
}
|
||||
|
||||
hot_reload_->enable();
|
||||
}
|
||||
|
||||
void shader_library::disable_hot_reload() {
|
||||
if (hot_reload_) {
|
||||
hot_reload_->disable();
|
||||
}
|
||||
}
|
||||
|
||||
bool shader_library::is_hot_reload_enabled() const noexcept {
|
||||
return hot_reload_ && hot_reload_->is_enabled();
|
||||
}
|
||||
|
||||
shader_load_result shader_library::load_internal(
|
||||
std::string_view name,
|
||||
const std::filesystem::path& full_path,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options) {
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
shader_load_result result;
|
||||
|
||||
// 合并编译选项
|
||||
shader_compile_options merged_options = config_.default_compile_options;
|
||||
merged_options.source_language = options.source_language;
|
||||
merged_options.optimization = options.optimization;
|
||||
merged_options.generate_debug_info = options.generate_debug_info;
|
||||
if (!options.entry_point.empty()) {
|
||||
merged_options.entry_point = options.entry_point;
|
||||
}
|
||||
for (const auto& [key, value] : options.defines) {
|
||||
merged_options.defines[key] = value;
|
||||
}
|
||||
|
||||
// 编译
|
||||
auto compile_result = compiler_.compile_from_file(full_path, stage, merged_options);
|
||||
|
||||
if (!compile_result.success) {
|
||||
result.success = false;
|
||||
result.error_message = compile_result.get_diagnostics_string();
|
||||
return result;
|
||||
}
|
||||
|
||||
// 创建模块
|
||||
shader_module_create_info create_info;
|
||||
create_info.stage = stage;
|
||||
create_info.spirv = std::move(compile_result.spirv);
|
||||
create_info.entry_point = merged_options.entry_point;
|
||||
create_info.name = std::string(name);
|
||||
create_info.source_path = full_path;
|
||||
|
||||
auto module = shader_module::create(device_, create_info);
|
||||
if (!module) {
|
||||
result.success = false;
|
||||
result.error_message = "Failed to create shader module";
|
||||
return result;
|
||||
}
|
||||
|
||||
// 存储
|
||||
std::string name_str(name);
|
||||
result.module = module.get();
|
||||
modules_[name_str] = std::move(module);
|
||||
module_sources_[name_str] = full_path;
|
||||
|
||||
// 注册热重载监视
|
||||
if (hot_reload_) {
|
||||
hot_reload_->watch(full_path, name);
|
||||
|
||||
// 注册依赖
|
||||
for (const auto& dep : compile_result.dependencies) {
|
||||
hot_reload_->add_dependency(name, dep);
|
||||
}
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void shader_library::on_hot_reload_event(const shader_reload_event& event) {
|
||||
if (!event.success) {
|
||||
// 编译失败,不更新
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找受影响的着色器
|
||||
std::string normalized_path = std::filesystem::weakly_canonical(event.source_path).string();
|
||||
|
||||
std::vector<std::string> affected_shaders;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
for (const auto& [name, path] : module_sources_) {
|
||||
std::string shader_path = std::filesystem::weakly_canonical(path).string();
|
||||
if (shader_path == normalized_path) {
|
||||
affected_shaders.push_back(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重新加载
|
||||
for (const auto& name : affected_shaders) {
|
||||
reload(name);
|
||||
}
|
||||
}
|
||||
|
||||
void shader_library::notify_shader_reload(const std::string& name, shader_module* module) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
for (const auto& callback : shader_reload_callbacks_) {
|
||||
if (callback) {
|
||||
callback(name, module);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void shader_library::notify_program_reload(const std::string& name, shader_program* program) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
for (const auto& callback : program_reload_callbacks_) {
|
||||
if (callback) {
|
||||
callback(name, program);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void shader_library::rebuild_dependent_programs(const std::string& shader_name) {
|
||||
std::vector<std::string> affected_programs;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
for (const auto& [prog_name, info] : program_sources_) {
|
||||
if (info.vertex_shader == shader_name ||
|
||||
info.fragment_shader == shader_name ||
|
||||
info.compute_shader == shader_name) {
|
||||
affected_programs.push_back(prog_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& prog_name : affected_programs) {
|
||||
reload_program(prog_name);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mirai
|
||||
} // namespace mirai
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
* @brief MIRAI 框架着色器库管理
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
*
|
||||
* @details
|
||||
* 本文件实现了着色器库管理系统,提供:
|
||||
* - 着色器模块和程序的集中管理
|
||||
* - 按名称加载和访问着色器
|
||||
* - 与热重载系统的集成
|
||||
* - 从头文件中加载预编译的 SPIR-V 数据
|
||||
* - 资源生命周期管理
|
||||
*
|
||||
* 注意:所有着色器由 tools/shader_compile 编译为 SPIR-V 并生成到头文件中
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@@ -17,11 +18,7 @@
|
||||
#include "shader_types.hpp"
|
||||
#include "shader_module.hpp"
|
||||
#include "shader_program.hpp"
|
||||
#include "shader_compiler.hpp"
|
||||
#include "shader_cache.hpp"
|
||||
#include "shader_hot_reload.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -35,29 +32,6 @@ typedef struct VkDevice_T* VkDevice;
|
||||
|
||||
namespace mirai {
|
||||
|
||||
/**
|
||||
* @brief 着色器库配置
|
||||
*/
|
||||
struct shader_library_config {
|
||||
/// 默认着色器搜索路径
|
||||
std::vector<std::filesystem::path> search_paths;
|
||||
|
||||
/// 是否启用热重载
|
||||
bool enable_hot_reload{false};
|
||||
|
||||
/// 是否启用缓存
|
||||
bool enable_cache{true};
|
||||
|
||||
/// 缓存配置
|
||||
shader_cache_config cache_config;
|
||||
|
||||
/// 热重载配置
|
||||
hot_reload_config hot_reload_config;
|
||||
|
||||
/// 默认编译选项
|
||||
shader_compile_options default_compile_options;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 着色器加载结果
|
||||
*/
|
||||
@@ -86,50 +60,35 @@ struct program_load_result {
|
||||
shader_program* program{nullptr};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 着色器重载回调类型
|
||||
*/
|
||||
using shader_reload_callback = std::function<void(const std::string& name, shader_module* new_module)>;
|
||||
|
||||
/**
|
||||
* @brief 程序重载回调类型
|
||||
*/
|
||||
using program_reload_callback = std::function<void(const std::string& name, shader_program* new_program)>;
|
||||
|
||||
/**
|
||||
* @brief 着色器库
|
||||
*
|
||||
*
|
||||
* @details
|
||||
* 集中管理所有着色器模块和程序,提供加载、访问、重载功能。
|
||||
*
|
||||
* 集中管理所有着色器模块和程序,从预编译的 SPIR-V 数据加载。
|
||||
* SPIR-V 数据由着色器编译器生成在头文件中。
|
||||
*
|
||||
* 使用示例:
|
||||
* @code
|
||||
* shader_library_config config;
|
||||
* config.search_paths = {"shaders/"};
|
||||
* config.enable_hot_reload = true;
|
||||
*
|
||||
* shader_library library(device, config);
|
||||
*
|
||||
* // 加载单个着色器
|
||||
* auto result = library.load("basic_vert", "basic.vert");
|
||||
* #include "generated/basic_shaders.hpp"
|
||||
*
|
||||
* shader_library library(device);
|
||||
*
|
||||
* // 从预编译的 SPIR-V 数据加载着色器
|
||||
* auto result = library.load_spirv_data(
|
||||
* "basic_vert",
|
||||
* basic_vert_spirv,
|
||||
* shader_stage::vertex
|
||||
* );
|
||||
* if (!result.success) {
|
||||
* std::cerr << "Failed: " << result.error_message << std::endl;
|
||||
* }
|
||||
*
|
||||
* // 加载着色器程序
|
||||
* auto prog_result = library.load_program("basic", "basic.vert", "basic.frag");
|
||||
*
|
||||
*
|
||||
* // 创建着色器程序
|
||||
* auto prog_result = library.create_program("basic", "basic_vert", "basic_frag");
|
||||
*
|
||||
* // 获取着色器
|
||||
* shader_module* vs = library.get("basic_vert");
|
||||
* shader_program* prog = library.get_program("basic");
|
||||
*
|
||||
* // 注册重载回调
|
||||
* library.on_shader_reload([](const std::string& name, shader_module* module) {
|
||||
* std::cout << "Shader reloaded: " << name << std::endl;
|
||||
* });
|
||||
*
|
||||
* // 主循环中更新(用于热重载)
|
||||
* library.update();
|
||||
* @endcode
|
||||
*/
|
||||
class shader_library {
|
||||
@@ -140,13 +99,6 @@ public:
|
||||
*/
|
||||
explicit shader_library(VkDevice device);
|
||||
|
||||
/**
|
||||
* @brief 带配置的构造函数
|
||||
* @param device Vulkan 设备
|
||||
* @param config 库配置
|
||||
*/
|
||||
shader_library(VkDevice device, const shader_library_config& config);
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
@@ -161,78 +113,33 @@ public:
|
||||
shader_library& operator=(shader_library&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 加载着色器
|
||||
* @brief 从 SPIR-V 数据加载着色器
|
||||
* @param name 着色器名称
|
||||
* @param path 文件路径(相对于搜索路径)
|
||||
* @param options 编译选项(可选)
|
||||
* @return 加载结果
|
||||
*/
|
||||
[[nodiscard]] shader_load_result load(
|
||||
std::string_view name,
|
||||
const std::filesystem::path& path,
|
||||
const shader_compile_options& options = {}
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 加载着色器(自动推断阶段)
|
||||
* @param name 着色器名称
|
||||
* @param path 文件路径
|
||||
* @param spirv SPIR-V 字节码
|
||||
* @param stage 着色器阶段
|
||||
* @param options 编译选项
|
||||
* @param entry_point 入口点名称
|
||||
* @return 加载结果
|
||||
*/
|
||||
[[nodiscard]] shader_load_result load(
|
||||
[[nodiscard]] shader_load_result load_spirv_data(
|
||||
std::string_view name,
|
||||
const std::filesystem::path& path,
|
||||
std::span<const u32> spirv,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options = {}
|
||||
std::string_view entry_point = "main"
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 从源码加载着色器
|
||||
* @brief 从 shader_binary 加载着色器
|
||||
* @param name 着色器名称
|
||||
* @param source 源码
|
||||
* @param stage 着色器阶段
|
||||
* @param options 编译选项
|
||||
* @param binary 着色器二进制数据
|
||||
* @return 加载结果
|
||||
*/
|
||||
[[nodiscard]] shader_load_result load_from_source(
|
||||
[[nodiscard]] shader_load_result load_binary(
|
||||
std::string_view name,
|
||||
std::string_view source,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options = {}
|
||||
const shader_binary& binary
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 加载着色器程序
|
||||
* @param name 程序名称
|
||||
* @param vertex_path 顶点着色器路径
|
||||
* @param fragment_path 片段着色器路径
|
||||
* @param options 编译选项
|
||||
* @return 加载结果
|
||||
*/
|
||||
[[nodiscard]] program_load_result load_program(
|
||||
std::string_view name,
|
||||
const std::filesystem::path& vertex_path,
|
||||
const std::filesystem::path& fragment_path,
|
||||
const shader_compile_options& options = {}
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 加载计算着色器程序
|
||||
* @param name 程序名称
|
||||
* @param compute_path 计算着色器路径
|
||||
* @param options 编译选项
|
||||
* @return 加载结果
|
||||
*/
|
||||
[[nodiscard]] program_load_result load_compute_program(
|
||||
std::string_view name,
|
||||
const std::filesystem::path& compute_path,
|
||||
const shader_compile_options& options = {}
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 创建程序(从已加载的模块)
|
||||
* @brief 创建图形着色器程序(从已加载的模块)
|
||||
* @param name 程序名称
|
||||
* @param vertex_name 顶点着色器名称
|
||||
* @param fragment_name 片段着色器名称
|
||||
@@ -244,6 +151,17 @@ public:
|
||||
std::string_view fragment_name
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 创建计算着色器程序(从已加载的模块)
|
||||
* @param name 程序名称
|
||||
* @param compute_name 计算着色器名称
|
||||
* @return 加载结果
|
||||
*/
|
||||
[[nodiscard]] program_load_result create_compute_program(
|
||||
std::string_view name,
|
||||
std::string_view compute_name
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 获取着色器模块
|
||||
* @param name 着色器名称
|
||||
@@ -291,31 +209,6 @@ public:
|
||||
*/
|
||||
void unload_all();
|
||||
|
||||
/**
|
||||
* @brief 重新加载着色器
|
||||
* @param name 着色器名称
|
||||
* @return 加载结果
|
||||
*/
|
||||
[[nodiscard]] shader_load_result reload(std::string_view name);
|
||||
|
||||
/**
|
||||
* @brief 重新加载程序
|
||||
* @param name 程序名称
|
||||
* @return 加载结果
|
||||
*/
|
||||
[[nodiscard]] program_load_result reload_program(std::string_view name);
|
||||
|
||||
/**
|
||||
* @brief 重新加载所有着色器
|
||||
* @return 重新加载的数量
|
||||
*/
|
||||
u32 reload_all();
|
||||
|
||||
/**
|
||||
* @brief 更新(处理热重载等)
|
||||
*/
|
||||
void update();
|
||||
|
||||
/**
|
||||
* @brief 获取所有着色器名称
|
||||
* @return 名称列表
|
||||
@@ -339,146 +232,19 @@ public:
|
||||
* @return 数量
|
||||
*/
|
||||
[[nodiscard]] u32 get_program_count() const;
|
||||
|
||||
/**
|
||||
* @brief 注册着色器重载回调
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
void on_shader_reload(shader_reload_callback callback);
|
||||
|
||||
/**
|
||||
* @brief 注册程序重载回调
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
void on_program_reload(program_reload_callback callback);
|
||||
|
||||
/**
|
||||
* @brief 获取编译器
|
||||
* @return 编译器引用
|
||||
*/
|
||||
[[nodiscard]] shader_compiler& get_compiler() { return compiler_; }
|
||||
|
||||
/**
|
||||
* @brief 获取缓存
|
||||
* @return 缓存指针,未启用返回 nullptr
|
||||
*/
|
||||
[[nodiscard]] shader_cache* get_cache() { return cache_.get(); }
|
||||
|
||||
/**
|
||||
* @brief 获取热重载管理器
|
||||
* @return 热重载指针,未启用返回 nullptr
|
||||
*/
|
||||
[[nodiscard]] shader_hot_reload* get_hot_reload() { return hot_reload_.get(); }
|
||||
|
||||
/**
|
||||
* @brief 获取配置
|
||||
* @return 配置引用
|
||||
*/
|
||||
[[nodiscard]] const shader_library_config& get_config() const noexcept { return config_; }
|
||||
|
||||
/**
|
||||
* @brief 添加搜索路径
|
||||
* @param path 搜索路径
|
||||
*/
|
||||
void add_search_path(const std::filesystem::path& path);
|
||||
|
||||
/**
|
||||
* @brief 查找文件
|
||||
* @param filename 文件名
|
||||
* @return 完整路径,未找到返回空
|
||||
*/
|
||||
[[nodiscard]] std::optional<std::filesystem::path> find_file(const std::filesystem::path& filename) const;
|
||||
|
||||
/**
|
||||
* @brief 启用热重载
|
||||
*/
|
||||
void enable_hot_reload();
|
||||
|
||||
/**
|
||||
* @brief 禁用热重载
|
||||
*/
|
||||
void disable_hot_reload();
|
||||
|
||||
/**
|
||||
* @brief 检查热重载是否启用
|
||||
* @return 是否启用
|
||||
*/
|
||||
[[nodiscard]] bool is_hot_reload_enabled() const noexcept;
|
||||
|
||||
private:
|
||||
/// Vulkan 设备
|
||||
VkDevice device_;
|
||||
|
||||
/// 配置
|
||||
shader_library_config config_;
|
||||
|
||||
/// 编译器
|
||||
shader_compiler compiler_;
|
||||
|
||||
/// 缓存
|
||||
std::unique_ptr<shader_cache> cache_;
|
||||
|
||||
/// 热重载
|
||||
std::unique_ptr<shader_hot_reload> hot_reload_;
|
||||
|
||||
/// 着色器模块映射
|
||||
std::unordered_map<std::string, shader_module_ptr> modules_;
|
||||
|
||||
/// 着色器程序映射
|
||||
std::unordered_map<std::string, shader_program_ptr> programs_;
|
||||
|
||||
/// 着色器源文件映射
|
||||
std::unordered_map<std::string, std::filesystem::path> module_sources_;
|
||||
|
||||
/// 程序组成信息
|
||||
struct program_info {
|
||||
std::string vertex_shader;
|
||||
std::string fragment_shader;
|
||||
std::string compute_shader;
|
||||
std::filesystem::path vertex_path;
|
||||
std::filesystem::path fragment_path;
|
||||
std::filesystem::path compute_path;
|
||||
};
|
||||
std::unordered_map<std::string, program_info> program_sources_;
|
||||
|
||||
/// 着色器重载回调
|
||||
std::vector<shader_reload_callback> shader_reload_callbacks_;
|
||||
|
||||
/// 程序重载回调
|
||||
std::vector<program_reload_callback> program_reload_callbacks_;
|
||||
|
||||
/// 互斥锁
|
||||
mutable std::mutex mutex_;
|
||||
|
||||
/**
|
||||
* @brief 内部加载着色器
|
||||
*/
|
||||
[[nodiscard]] shader_load_result load_internal(
|
||||
std::string_view name,
|
||||
const std::filesystem::path& full_path,
|
||||
shader_stage stage,
|
||||
const shader_compile_options& options
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 处理热重载事件
|
||||
*/
|
||||
void on_hot_reload_event(const shader_reload_event& event);
|
||||
|
||||
/**
|
||||
* @brief 通知着色器重载
|
||||
*/
|
||||
void notify_shader_reload(const std::string& name, shader_module* module);
|
||||
|
||||
/**
|
||||
* @brief 通知程序重载
|
||||
*/
|
||||
void notify_program_reload(const std::string& name, shader_program* program);
|
||||
|
||||
/**
|
||||
* @brief 重建使用指定着色器的程序
|
||||
*/
|
||||
void rebuild_dependent_programs(const std::string& shader_name);
|
||||
};
|
||||
|
||||
} // namespace mirai
|
||||
} // namespace mirai
|
||||
|
||||
@@ -69,7 +69,6 @@ bool shader_module::initialize(vk::Device device, const shader_module_create_inf
|
||||
stage_ = create_info.stage;
|
||||
entry_point_ = create_info.entry_point;
|
||||
spirv_ = create_info.spirv;
|
||||
source_path_ = create_info.source_path;
|
||||
|
||||
#if MIRAI_DEBUG
|
||||
if (!create_info.name.empty()) {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* @details
|
||||
* 本文件定义了着色器模块类,封装了 Vulkan VkShaderModule,
|
||||
* 并持有 SPIR-V 字节码和反射信息。
|
||||
*
|
||||
* SPIR-V 数据由着色器编译器直接生成在头文件中。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@@ -17,7 +19,6 @@
|
||||
|
||||
#include "vulkan_types.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
@@ -42,9 +43,6 @@ struct shader_module_create_info {
|
||||
/// 模块名称(用于调试)
|
||||
std::string name;
|
||||
|
||||
/// 源文件路径(可选,用于热重载)
|
||||
std::filesystem::path source_path;
|
||||
|
||||
/**
|
||||
* @brief 从 shader_binary 创建
|
||||
* @param binary 着色器二进制
|
||||
@@ -63,23 +61,30 @@ struct shader_module_create_info {
|
||||
* @brief 着色器模块
|
||||
*
|
||||
* @details
|
||||
* 封装了一个编译后的着色器阶段,持有:
|
||||
* 封装了一个预编译的着色器阶段,持有:
|
||||
* - Vulkan VkShaderModule 句柄
|
||||
* - SPIR-V 字节码(用于反射和缓存)
|
||||
* - SPIR-V 字节码(用于反射)
|
||||
* - 反射信息
|
||||
*
|
||||
*
|
||||
* 使用示例:
|
||||
* @code
|
||||
* // 从编译结果创建
|
||||
* shader_compile_result compile_result = compiler.compile_from_file("shader.vert", shader_stage::vertex);
|
||||
*
|
||||
* shader_module_create_info info;
|
||||
* info.stage = shader_stage::vertex;
|
||||
* info.spirv = compile_result.spirv;
|
||||
* info.entry_point = "main";
|
||||
*
|
||||
* auto module = shader_module::create(device, info);
|
||||
* // 从预编译的 SPIR-V 数据创建(由编译器生成在头文件中)
|
||||
* #include "generated_shaders/basic_shaders.hpp"
|
||||
*
|
||||
* auto module = shader_module::create_from_spirv(
|
||||
* device,
|
||||
* basic_vert_spirv,
|
||||
* shader_stage::vertex
|
||||
* );
|
||||
*
|
||||
* // 或者从 shader_binary 创建
|
||||
* shader_binary binary;
|
||||
* binary.stage = shader_stage::vertex;
|
||||
* binary.spirv = basic_vert_spirv;
|
||||
* binary.entry_point = "main";
|
||||
*
|
||||
* auto module2 = shader_module::create_from_binary(device, binary);
|
||||
*
|
||||
* // 使用模块
|
||||
* VkShaderModule vk_module = module->get_vulkan_module();
|
||||
* const auto& reflection = module->get_reflection();
|
||||
@@ -165,20 +170,6 @@ public:
|
||||
*/
|
||||
[[nodiscard]] vk::ShaderModule get_vulkan_module() const noexcept { return module_; }
|
||||
|
||||
/**
|
||||
* @brief 获取源文件路径
|
||||
* @return 源文件路径
|
||||
*/
|
||||
[[nodiscard]] const std::filesystem::path& get_source_path() const noexcept {
|
||||
return source_path_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置源文件路径
|
||||
* @param path 源文件路径
|
||||
*/
|
||||
void set_source_path(const std::filesystem::path& path) { source_path_ = path; }
|
||||
|
||||
/**
|
||||
* @brief 检查模块是否有效
|
||||
* @return 是否有效
|
||||
@@ -292,9 +283,6 @@ private:
|
||||
|
||||
/// 反射信息
|
||||
shader_reflection reflection_;
|
||||
|
||||
/// 源文件路径
|
||||
std::filesystem::path source_path_;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "types/types.hpp"
|
||||
#include "shader_types.hpp"
|
||||
|
||||
@@ -130,9 +128,10 @@ struct shader_reflection_data {
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* std::vector<u32> spirv = load_spirv_file("shader.spv");
|
||||
* #include "generated/shader_spv.hpp"
|
||||
*
|
||||
* shader_reflection reflection;
|
||||
* if (reflection.reflect(spirv)) {
|
||||
* if (reflection.reflect(basic_vert_spirv)) {
|
||||
* auto& inputs = reflection.get_input_attributes();
|
||||
* auto& uniforms = reflection.get_uniform_buffers();
|
||||
* // ...
|
||||
@@ -450,13 +449,6 @@ private:
|
||||
// 辅助函数
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 从 SPIR-V 文件加载并创建反射
|
||||
* @param path 文件路径
|
||||
* @return 反射对象,如果失败则 is_valid() 返回 false
|
||||
*/
|
||||
[[nodiscard]] shader_reflection load_reflection_from_file(const std::filesystem::path& path);
|
||||
|
||||
/**
|
||||
* @brief 合并多个着色器阶段的反射信息
|
||||
* @param reflections 反射对象列表
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
#include <array>
|
||||
#include <span>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
@@ -1162,4 +1163,322 @@ struct workgroup_size {
|
||||
}
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// 静态反射元数据结构(支持 constexpr 初始化)
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 静态 Uniform 成员信息
|
||||
*
|
||||
* 用于编译时反射,支持 constexpr 初始化
|
||||
*/
|
||||
struct static_uniform_member {
|
||||
/// 成员名称
|
||||
const char* name{nullptr};
|
||||
|
||||
/// 类型名称(如 "float", "vec4", "mat4")
|
||||
const char* type{nullptr};
|
||||
|
||||
/// 在 Uniform Buffer 中的偏移量(字节)
|
||||
u32 offset{0};
|
||||
|
||||
/// 成员大小(字节)
|
||||
u32 size{0};
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
constexpr static_uniform_member() = default;
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param n 成员名称
|
||||
* @param t 类型名称
|
||||
* @param off 偏移量
|
||||
* @param sz 大小
|
||||
*/
|
||||
constexpr static_uniform_member(const char* n, const char* t, u32 off, u32 sz)
|
||||
: name(n), type(t), offset(off), size(sz) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 静态 Uniform Block 信息
|
||||
*
|
||||
* 用于编译时反射,支持 constexpr 初始化
|
||||
*/
|
||||
struct static_uniform_block_info {
|
||||
/// Block 名称
|
||||
const char* name{nullptr};
|
||||
|
||||
/// 描述符集索引
|
||||
u32 set{0};
|
||||
|
||||
/// 绑定点索引
|
||||
u32 binding{0};
|
||||
|
||||
/// Block 总大小(字节)
|
||||
u32 size{0};
|
||||
|
||||
/// 成员列表
|
||||
std::span<const static_uniform_member> members{};
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
constexpr static_uniform_block_info() = default;
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param n Block 名称
|
||||
* @param s 描述符集索引
|
||||
* @param b 绑定点索引
|
||||
* @param sz 大小
|
||||
* @param m 成员列表
|
||||
*/
|
||||
constexpr static_uniform_block_info(const char* n, u32 s, u32 b, u32 sz,
|
||||
std::span<const static_uniform_member> m = {})
|
||||
: name(n), set(s), binding(b), size(sz), members(m) {}
|
||||
|
||||
/**
|
||||
* @brief 查找成员
|
||||
* @param member_name 成员名称
|
||||
* @return 成员信息的指针,如果未找到返回 nullptr
|
||||
*/
|
||||
[[nodiscard]] constexpr const static_uniform_member* find_member(std::string_view member_name) const noexcept {
|
||||
for (const auto& member : members) {
|
||||
if (member.name && member_name == member.name) {
|
||||
return &member;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 静态纹理/采样器信息
|
||||
*
|
||||
* 用于编译时反射,支持 constexpr 初始化
|
||||
*/
|
||||
struct static_texture_info {
|
||||
/// 纹理名称
|
||||
const char* name{nullptr};
|
||||
|
||||
/// 描述符集索引
|
||||
u32 set{0};
|
||||
|
||||
/// 绑定点索引
|
||||
u32 binding{0};
|
||||
|
||||
/// 纹理类型(如 "texture2D", "textureCube", "texture2DArray")
|
||||
const char* type{nullptr};
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
constexpr static_texture_info() = default;
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param n 纹理名称
|
||||
* @param s 描述符集索引
|
||||
* @param b 绑定点索引
|
||||
* @param t 纹理类型
|
||||
*/
|
||||
constexpr static_texture_info(const char* n, u32 s, u32 b, const char* t)
|
||||
: name(n), set(s), binding(b), type(t) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 静态 Push Constant 成员信息
|
||||
*
|
||||
* 用于编译时反射,支持 constexpr 初始化
|
||||
*/
|
||||
struct static_push_constant_member {
|
||||
/// 成员名称
|
||||
const char* name{nullptr};
|
||||
|
||||
/// 类型名称
|
||||
const char* type{nullptr};
|
||||
|
||||
/// 偏移量(字节)
|
||||
u32 offset{0};
|
||||
|
||||
/// 大小(字节)
|
||||
u32 size{0};
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
constexpr static_push_constant_member() = default;
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
constexpr static_push_constant_member(const char* n, const char* t, u32 off, u32 sz)
|
||||
: name(n), type(t), offset(off), size(sz) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 静态 Push Constant 信息
|
||||
*
|
||||
* 用于编译时反射,支持 constexpr 初始化
|
||||
*/
|
||||
struct static_push_constant_info {
|
||||
/// Block 名称
|
||||
const char* name{nullptr};
|
||||
|
||||
/// 着色器阶段标志位
|
||||
u32 stage_flags{0};
|
||||
|
||||
/// 偏移量(字节)
|
||||
u32 offset{0};
|
||||
|
||||
/// 总大小(字节)
|
||||
u32 size{0};
|
||||
|
||||
/// 成员列表
|
||||
std::span<const static_push_constant_member> members{};
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
constexpr static_push_constant_info() = default;
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
constexpr static_push_constant_info(const char* n, u32 flags, u32 off, u32 sz,
|
||||
std::span<const static_push_constant_member> m = {})
|
||||
: name(n), stage_flags(flags), offset(off), size(sz), members(m) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 静态顶点输入属性信息
|
||||
*
|
||||
* 用于编译时反射,支持 constexpr 初始化
|
||||
*/
|
||||
struct static_vertex_input_info {
|
||||
/// 属性名称
|
||||
const char* name{nullptr};
|
||||
|
||||
/// 位置索引(location)
|
||||
u32 location{0};
|
||||
|
||||
/// 类型名称
|
||||
const char* type{nullptr};
|
||||
|
||||
/// Vulkan 格式
|
||||
u32 format{0};
|
||||
|
||||
/// 偏移量(字节)
|
||||
u32 offset{0};
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
constexpr static_vertex_input_info() = default;
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
constexpr static_vertex_input_info(const char* n, u32 loc, const char* t, u32 fmt, u32 off)
|
||||
: name(n), location(loc), type(t), format(fmt), offset(off) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 静态着色器反射信息
|
||||
*
|
||||
* 聚合所有着色器反射数据,支持 constexpr 初始化
|
||||
*/
|
||||
struct static_shader_reflection {
|
||||
/// 着色器名称
|
||||
const char* name{nullptr};
|
||||
|
||||
/// Uniform Blocks 列表
|
||||
std::span<const static_uniform_block_info> uniform_blocks{};
|
||||
|
||||
/// 纹理/采样器列表
|
||||
std::span<const static_texture_info> textures{};
|
||||
|
||||
/// Push Constants 列表
|
||||
std::span<const static_push_constant_info> push_constants{};
|
||||
|
||||
/// 顶点输入属性列表
|
||||
std::span<const static_vertex_input_info> vertex_inputs{};
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
constexpr static_shader_reflection() = default;
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
constexpr static_shader_reflection(
|
||||
const char* n,
|
||||
std::span<const static_uniform_block_info> ub = {},
|
||||
std::span<const static_texture_info> tex = {},
|
||||
std::span<const static_push_constant_info> pc = {},
|
||||
std::span<const static_vertex_input_info> vi = {})
|
||||
: name(n), uniform_blocks(ub), textures(tex), push_constants(pc), vertex_inputs(vi) {}
|
||||
|
||||
/**
|
||||
* @brief 查找 Uniform Block
|
||||
* @param block_name Block 名称
|
||||
* @return Block 信息的指针,如果未找到返回 nullptr
|
||||
*/
|
||||
[[nodiscard]] constexpr const static_uniform_block_info* find_uniform_block(std::string_view block_name) const noexcept {
|
||||
for (const auto& block : uniform_blocks) {
|
||||
if (block.name && block_name == block.name) {
|
||||
return █
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查找纹理
|
||||
* @param texture_name 纹理名称
|
||||
* @return 纹理信息的指针,如果未找到返回 nullptr
|
||||
*/
|
||||
[[nodiscard]] constexpr const static_texture_info* find_texture(std::string_view texture_name) const noexcept {
|
||||
for (const auto& tex : textures) {
|
||||
if (tex.name && texture_name == tex.name) {
|
||||
return &tex;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 set 和 binding 查找 Uniform Block
|
||||
* @param set 描述符集索引
|
||||
* @param binding 绑定点索引
|
||||
* @return Block 信息的指针,如果未找到返回 nullptr
|
||||
*/
|
||||
[[nodiscard]] constexpr const static_uniform_block_info* find_uniform_block_by_binding(u32 set, u32 binding) const noexcept {
|
||||
for (const auto& block : uniform_blocks) {
|
||||
if (block.set == set && block.binding == binding) {
|
||||
return █
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 set 和 binding 查找纹理
|
||||
* @param set 描述符集索引
|
||||
* @param binding 绑定点索引
|
||||
* @return 纹理信息的指针,如果未找到返回 nullptr
|
||||
*/
|
||||
[[nodiscard]] constexpr const static_texture_info* find_texture_by_binding(u32 set, u32 binding) const noexcept {
|
||||
for (const auto& tex : textures) {
|
||||
if (tex.set == set && tex.binding == binding) {
|
||||
return &tex;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mirai
|
||||
60
src/shader/shaders/glsl/examples/blur.frag
Normal file
60
src/shader/shaders/glsl/examples/blur.frag
Normal file
@@ -0,0 +1,60 @@
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 高斯模糊效果示例 (Backdrop 模式)
|
||||
// 使用用户 UBO (Set 1) 传递模糊参数
|
||||
// ============================================================
|
||||
|
||||
// 框架输入
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
// 背景纹理(框架提供)
|
||||
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
|
||||
|
||||
// Set 1: 用户 UBO - 模糊参数
|
||||
layout(set = 1, binding = 0, std140) uniform BlurParams {
|
||||
float sigma; // 高斯分布标准差
|
||||
int samples; // 采样半径(实际采样数为 (2*samples+1)^2)
|
||||
vec2 texel_size; // 纹理像素大小 (1.0/width, 1.0/height)
|
||||
} params;
|
||||
|
||||
void main() {
|
||||
vec4 color = vec4(0.0);
|
||||
float total_weight = 0.0;
|
||||
|
||||
// 高斯模糊核
|
||||
float sigma2 = params.sigma * params.sigma;
|
||||
float inv_2sigma2 = 1.0 / (2.0 * sigma2);
|
||||
|
||||
// 双重循环采样
|
||||
for (int i = -params.samples; i <= params.samples; i++) {
|
||||
for (int j = -params.samples; j <= params.samples; j++) {
|
||||
// 计算偏移
|
||||
vec2 offset = vec2(float(i), float(j)) * params.texel_size;
|
||||
|
||||
// 计算高斯权重
|
||||
float dist2 = float(i * i + j * j);
|
||||
float weight = exp(-dist2 * inv_2sigma2);
|
||||
|
||||
// 累加采样
|
||||
color += texture(u_backdrop, v_uv + offset) * weight;
|
||||
total_weight += weight;
|
||||
}
|
||||
}
|
||||
|
||||
// 归一化
|
||||
frag_color = color / total_weight;
|
||||
|
||||
// 应用框架透明度
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
42
src/shader/shaders/glsl/examples/gradient.frag
Normal file
42
src/shader/shaders/glsl/examples/gradient.frag
Normal file
@@ -0,0 +1,42 @@
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 线性渐变效果示例 (Procedural 模式)
|
||||
// 使用 Push Constant 传递颜色和角度
|
||||
// ============================================================
|
||||
|
||||
// 框架输入
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
// Push Constant: 渐变参数
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color_start; // 起始颜色
|
||||
vec4 color_end; // 结束颜色
|
||||
float angle; // 渐变角度(弧度)
|
||||
float _pad[3];
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
// 计算渐变方向向量
|
||||
vec2 dir = vec2(cos(pc.angle), sin(pc.angle));
|
||||
|
||||
// 计算当前位置在渐变方向上的投影
|
||||
// v_local_pos 范围是 0-1,将其转换到 -0.5 到 0.5 范围
|
||||
float t = dot(v_local_pos - 0.5, dir) + 0.5;
|
||||
|
||||
// 线性插值颜色
|
||||
frag_color = mix(pc.color_start, pc.color_end, clamp(t, 0.0, 1.0));
|
||||
|
||||
// 应用框架透明度
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
39
src/shader/shaders/glsl/examples/particle.frag
Normal file
39
src/shader/shaders/glsl/examples/particle.frag
Normal file
@@ -0,0 +1,39 @@
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 粒子系统片段着色器 (Custom Mesh 模式)
|
||||
// 绘制圆形粒子,带软边缘
|
||||
// ============================================================
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec4 v_color;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
void main() {
|
||||
// 计算到中心的距离
|
||||
vec2 center = v_uv - 0.5;
|
||||
float dist = length(center) * 2.0;
|
||||
|
||||
// 圆形遮罩,带软边缘
|
||||
float alpha = 1.0 - smoothstep(0.7, 1.0, dist);
|
||||
|
||||
// 添加发光效果
|
||||
float glow = exp(-dist * dist * 2.0);
|
||||
|
||||
frag_color = v_color;
|
||||
frag_color.rgb += glow * 0.3; // 添加一点发光
|
||||
frag_color.a *= alpha * widget.u_opacity;
|
||||
|
||||
// 丢弃完全透明的片段
|
||||
if (frag_color.a < 0.01) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
73
src/shader/shaders/glsl/examples/particle.vert
Normal file
73
src/shader/shaders/glsl/examples/particle.vert
Normal file
@@ -0,0 +1,73 @@
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 粒子系统顶点着色器 (Custom Mesh 模式)
|
||||
// 使用实例化渲染绘制粒子
|
||||
// ============================================================
|
||||
|
||||
// 四边形顶点(Billboard)- 使用 gl_VertexIndex 生成
|
||||
const vec2 QUAD_VERTICES[6] = vec2[](
|
||||
vec2(-0.5, -0.5), vec2(0.5, -0.5), vec2(0.5, 0.5),
|
||||
vec2(-0.5, -0.5), vec2(0.5, 0.5), vec2(-0.5, 0.5)
|
||||
);
|
||||
|
||||
const vec2 QUAD_UVS[6] = vec2[](
|
||||
vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(1.0, 1.0),
|
||||
vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 1.0)
|
||||
);
|
||||
|
||||
// 实例数据输入
|
||||
layout(location = 0) in vec3 a_position; // 粒子位置
|
||||
layout(location = 1) in float a_size; // 粒子大小
|
||||
layout(location = 2) in vec4 a_color; // 粒子颜色
|
||||
layout(location = 3) in float a_rotation; // 粒子旋转
|
||||
|
||||
// 输出到片段着色器
|
||||
layout(location = 0) out vec2 v_uv;
|
||||
layout(location = 1) out vec4 v_color;
|
||||
|
||||
// Set 0: 框架专用
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds; // (x, y, width, height)
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
// Push Constant: 变换参数
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 view_proj;
|
||||
vec2 viewport_size;
|
||||
vec2 _pad;
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
// 获取四边形顶点
|
||||
int vertex_id = gl_VertexIndex % 6;
|
||||
vec2 quad_pos = QUAD_VERTICES[vertex_id];
|
||||
v_uv = QUAD_UVS[vertex_id];
|
||||
|
||||
// 应用旋转
|
||||
float c = cos(a_rotation);
|
||||
float s = sin(a_rotation);
|
||||
mat2 rot = mat2(c, -s, s, c);
|
||||
quad_pos = rot * quad_pos;
|
||||
|
||||
// 应用大小(转换为归一化坐标)
|
||||
vec2 size_normalized = a_size / pc.viewport_size;
|
||||
quad_pos *= size_normalized;
|
||||
|
||||
// 计算最终位置
|
||||
// a_position 是归一化坐标 (0-1)
|
||||
vec2 pos = a_position.xy + quad_pos;
|
||||
|
||||
// 应用 widget bounds
|
||||
pos = pos * widget.u_bounds.zw + widget.u_bounds.xy;
|
||||
|
||||
// 转换到 NDC (-1 到 1)
|
||||
vec2 ndc = pos * 2.0 - 1.0;
|
||||
|
||||
// Vulkan Y轴翻转
|
||||
gl_Position = vec4(ndc.x, -ndc.y, a_position.z, 1.0);
|
||||
|
||||
v_color = a_color;
|
||||
}
|
||||
53
src/shader/shaders/glsl/examples/rounded_rect.frag
Normal file
53
src/shader/shaders/glsl/examples/rounded_rect.frag
Normal file
@@ -0,0 +1,53 @@
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 圆角矩形遮罩示例 (Mask 模式)
|
||||
// 使用 Push Constant 传递圆角半径
|
||||
// ============================================================
|
||||
|
||||
// 框架输入
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 1) in vec2 v_local_pos;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds;
|
||||
float u_opacity;
|
||||
float _pad0, _pad1, _pad2;
|
||||
} widget;
|
||||
|
||||
// Push Constant: 遮罩参数
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color; // 遮罩颜色
|
||||
float radius; // 圆角半径(像素)
|
||||
float _pad[3];
|
||||
} pc;
|
||||
|
||||
// 计算圆角矩形的有符号距离场 (SDF)
|
||||
float rounded_rect_sdf(vec2 p, vec2 half_size, float radius) {
|
||||
vec2 q = abs(p) - half_size + radius;
|
||||
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
|
||||
}
|
||||
|
||||
void main() {
|
||||
// 获取 widget 尺寸
|
||||
vec2 size = widget.u_bounds.zw;
|
||||
vec2 half_size = size * 0.5;
|
||||
|
||||
// 将局部坐标转换到以中心为原点的坐标系
|
||||
// v_local_pos 范围是 0-1,转换到 -half_size 到 half_size
|
||||
vec2 p = (v_local_pos - 0.5) * size;
|
||||
|
||||
// 计算 SDF
|
||||
float d = rounded_rect_sdf(p, half_size, pc.radius);
|
||||
|
||||
// 使用 smoothstep 进行抗锯齿
|
||||
// d < 0 表示在形状内部,d > 0 表示在形状外部
|
||||
float alpha = 1.0 - smoothstep(-1.0, 1.0, d);
|
||||
|
||||
// 输出遮罩颜色
|
||||
frag_color = pc.color;
|
||||
frag_color.a *= alpha * widget.u_opacity;
|
||||
}
|
||||
41
src/shader/shaders/glsl/framework/fullscreen.vert
Normal file
41
src/shader/shaders/glsl/framework/fullscreen.vert
Normal file
@@ -0,0 +1,41 @@
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 框架内置全屏顶点着色器
|
||||
// 用于全屏后处理效果
|
||||
// ============================================================
|
||||
|
||||
// 无顶点输入 - 使用 gl_VertexIndex 生成全屏三角形
|
||||
|
||||
// 输出到片段着色器
|
||||
layout(location = 0) out vec2 v_uv;
|
||||
layout(location = 1) out vec2 v_local_pos;
|
||||
|
||||
// 框架 UBO - Set 0 专用
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds; // (x, y, width, height) - 归一化屏幕坐标
|
||||
float u_opacity; // 0.0 - 1.0
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
void main() {
|
||||
// 生成覆盖整个屏幕的三角形
|
||||
// 使用一个大三角形覆盖整个视口,比两个三角形更高效
|
||||
vec2 positions[3] = vec2[3](
|
||||
vec2(-1.0, -1.0),
|
||||
vec2( 3.0, -1.0),
|
||||
vec2(-1.0, 3.0)
|
||||
);
|
||||
|
||||
vec2 uvs[3] = vec2[3](
|
||||
vec2(0.0, 0.0),
|
||||
vec2(2.0, 0.0),
|
||||
vec2(0.0, 2.0)
|
||||
);
|
||||
|
||||
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
|
||||
v_uv = uvs[gl_VertexIndex];
|
||||
v_local_pos = uvs[gl_VertexIndex];
|
||||
}
|
||||
38
src/shader/shaders/glsl/framework/quad.vert
Normal file
38
src/shader/shaders/glsl/framework/quad.vert
Normal file
@@ -0,0 +1,38 @@
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// 框架内置四边形顶点着色器
|
||||
// 用于 Backdrop/Procedural/Mask 模式
|
||||
// ============================================================
|
||||
|
||||
// 顶点输入
|
||||
layout(location = 0) in vec2 a_position; // 顶点位置 (0-1 范围)
|
||||
layout(location = 1) in vec2 a_uv; // UV 坐标
|
||||
|
||||
// 输出到片段着色器
|
||||
layout(location = 0) out vec2 v_uv;
|
||||
layout(location = 1) out vec2 v_local_pos;
|
||||
|
||||
// 框架 UBO - Set 0 专用
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds; // (x, y, width, height) - 归一化屏幕坐标
|
||||
float u_opacity; // 0.0 - 1.0
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
void main() {
|
||||
// 将顶点位置转换到 NDC
|
||||
// a_position 是 0-1 范围,widget.u_bounds.xy 是左上角位置,zw 是宽高
|
||||
vec2 pos = a_position * widget.u_bounds.zw + widget.u_bounds.xy;
|
||||
|
||||
// 转换到 NDC (-1 到 1)
|
||||
vec2 ndc = pos * 2.0 - 1.0;
|
||||
|
||||
// Vulkan Y轴翻转
|
||||
gl_Position = vec4(ndc.x, -ndc.y, 0.0, 1.0);
|
||||
|
||||
v_uv = a_uv;
|
||||
v_local_pos = a_position;
|
||||
}
|
||||
62
src/shader/shaders/glsl/templates/backdrop.frag.template
Normal file
62
src/shader/shaders/glsl/templates/backdrop.frag.template
Normal file
@@ -0,0 +1,62 @@
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// Backdrop 模式片段着色器模板
|
||||
// 用于后处理效果,框架提供背景纹理
|
||||
// ============================================================
|
||||
|
||||
// ============================================================
|
||||
// 框架输入(不要修改)
|
||||
// ============================================================
|
||||
layout(location = 0) in vec2 v_uv; // UV 坐标 (0-1)
|
||||
layout(location = 1) in vec2 v_local_pos; // 局部坐标 (0-1),相对于 widget bounds
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用(不要修改)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds; // (x, y, width, height)
|
||||
float u_opacity; // 0.0 - 1.0
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// 背景纹理(框架提供)
|
||||
layout(set = 0, binding = 1) uniform sampler2D u_backdrop;
|
||||
|
||||
// ============================================================
|
||||
// 用户区域(自由定义)
|
||||
// ============================================================
|
||||
|
||||
// Set 1: 用户 UBO(可选)
|
||||
// layout(set = 1, binding = 0, std140) uniform MyParams {
|
||||
// float radius;
|
||||
// int samples;
|
||||
// vec2 texel_size;
|
||||
// // ... 自定义参数
|
||||
// } params;
|
||||
|
||||
// Set 2: 用户纹理(可选)
|
||||
// layout(set = 2, binding = 0) uniform sampler2D u_my_texture;
|
||||
|
||||
// Push Constant(可选,最大 128 bytes)
|
||||
// layout(push_constant) uniform PushConstants {
|
||||
// // ... 自定义参数
|
||||
// } pc;
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
void main() {
|
||||
// 采样背景纹理
|
||||
vec4 color = texture(u_backdrop, v_uv);
|
||||
|
||||
// TODO: 用户实现后处理效果
|
||||
// 例如:模糊、色彩调整、扭曲等
|
||||
|
||||
frag_color = color;
|
||||
|
||||
// 应用框架提供的透明度
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
62
src/shader/shaders/glsl/templates/mask.frag.template
Normal file
62
src/shader/shaders/glsl/templates/mask.frag.template
Normal file
@@ -0,0 +1,62 @@
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// Mask 模式片段着色器模板
|
||||
// 用于遮罩效果,输出单通道 alpha 值
|
||||
// ============================================================
|
||||
|
||||
// ============================================================
|
||||
// 框架输入(不要修改)
|
||||
// ============================================================
|
||||
layout(location = 0) in vec2 v_uv; // UV 坐标 (0-1)
|
||||
layout(location = 1) in vec2 v_local_pos; // 局部坐标 (0-1),相对于 widget bounds
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用(不要修改)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds; // (x, y, width, height)
|
||||
float u_opacity; // 0.0 - 1.0
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// ============================================================
|
||||
// 用户区域(自由定义)
|
||||
// ============================================================
|
||||
|
||||
// Set 1: 用户 UBO(可选)
|
||||
// layout(set = 1, binding = 0, std140) uniform MyParams {
|
||||
// // ... 自定义参数
|
||||
// } params;
|
||||
|
||||
// Set 2: 用户纹理(可选)
|
||||
// layout(set = 2, binding = 0) uniform sampler2D u_mask_texture;
|
||||
|
||||
// Push Constant(可选,最大 128 bytes)
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color; // 遮罩颜色
|
||||
float corner_radius; // 圆角半径
|
||||
float _pad[3];
|
||||
} pc;
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
void main() {
|
||||
// TODO: 用户实现遮罩计算
|
||||
|
||||
// 示例:圆角矩形遮罩
|
||||
vec2 size = widget.u_bounds.zw;
|
||||
vec2 half_size = size * 0.5;
|
||||
vec2 p = (v_local_pos - 0.5) * size;
|
||||
|
||||
vec2 q = abs(p) - half_size + pc.corner_radius;
|
||||
float d = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - pc.corner_radius;
|
||||
float mask = 1.0 - smoothstep(-1.0, 1.0, d);
|
||||
|
||||
// 输出遮罩颜色,alpha 通道包含遮罩值
|
||||
frag_color = pc.color;
|
||||
frag_color.a *= mask * widget.u_opacity;
|
||||
}
|
||||
54
src/shader/shaders/glsl/templates/mesh.vert.template
Normal file
54
src/shader/shaders/glsl/templates/mesh.vert.template
Normal file
@@ -0,0 +1,54 @@
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// Custom Mesh 模式顶点着色器模板
|
||||
// 用于自定义网格渲染,用户需要同时提供顶点和片段着色器
|
||||
// ============================================================
|
||||
|
||||
// ============================================================
|
||||
// 用户定义顶点输入
|
||||
// ============================================================
|
||||
layout(location = 0) in vec3 a_position; // 顶点位置
|
||||
layout(location = 1) in vec2 a_uv; // UV 坐标
|
||||
layout(location = 2) in vec4 a_color; // 顶点颜色
|
||||
|
||||
// 输出到片段着色器
|
||||
layout(location = 0) out vec2 v_uv;
|
||||
layout(location = 1) out vec4 v_color;
|
||||
|
||||
// Set 0: 框架专用(不要修改)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds; // (x, y, width, height)
|
||||
float u_opacity; // 0.0 - 1.0
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// ============================================================
|
||||
// 用户区域(自由定义)
|
||||
// ============================================================
|
||||
|
||||
// Set 1: 用户 UBO(可选)
|
||||
// layout(set = 1, binding = 0, std140) uniform MyParams {
|
||||
// mat4 view;
|
||||
// mat4 projection;
|
||||
// // ... 自定义参数
|
||||
// } params;
|
||||
|
||||
// Push Constant(可选,最大 128 bytes)
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 mvp; // Model-View-Projection 矩阵
|
||||
} pc;
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
void main() {
|
||||
// 使用 MVP 矩阵变换顶点位置
|
||||
gl_Position = pc.mvp * vec4(a_position, 1.0);
|
||||
|
||||
// 传递数据到片段着色器
|
||||
v_uv = a_uv;
|
||||
v_color = a_color;
|
||||
}
|
||||
55
src/shader/shaders/glsl/templates/procedural.frag.template
Normal file
55
src/shader/shaders/glsl/templates/procedural.frag.template
Normal file
@@ -0,0 +1,55 @@
|
||||
#version 450
|
||||
|
||||
// ============================================================
|
||||
// Procedural 模式片段着色器模板
|
||||
// 用于程序化生成效果
|
||||
// ============================================================
|
||||
|
||||
// ============================================================
|
||||
// 框架输入(不要修改)
|
||||
// ============================================================
|
||||
layout(location = 0) in vec2 v_uv; // UV 坐标 (0-1)
|
||||
layout(location = 1) in vec2 v_local_pos; // 局部坐标 (0-1),相对于 widget bounds
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
// Set 0: 框架专用(不要修改)
|
||||
layout(set = 0, binding = 0, std140) uniform WidgetBounds {
|
||||
vec4 u_bounds; // (x, y, width, height)
|
||||
float u_opacity; // 0.0 - 1.0
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
float _pad2;
|
||||
} widget;
|
||||
|
||||
// ============================================================
|
||||
// 用户区域(自由定义)
|
||||
// ============================================================
|
||||
|
||||
// Set 1: 用户 UBO(可选)
|
||||
// layout(set = 1, binding = 0, std140) uniform MyParams {
|
||||
// float time;
|
||||
// // ... 自定义参数
|
||||
// } params;
|
||||
|
||||
// Set 2: 用户纹理(可选)
|
||||
// layout(set = 2, binding = 0) uniform sampler2D u_my_texture;
|
||||
|
||||
// Push Constant(可选,最大 128 bytes)
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color;
|
||||
// ... 自定义参数
|
||||
} pc;
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
void main() {
|
||||
// TODO: 用户实现程序化效果
|
||||
|
||||
// 示例:使用 Push Constant 中的颜色
|
||||
frag_color = pc.color;
|
||||
|
||||
// 应用框架提供的透明度
|
||||
frag_color.a *= widget.u_opacity;
|
||||
}
|
||||
342
src/ui/backdrop_widget.hpp
Normal file
342
src/ui/backdrop_widget.hpp
Normal file
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* @file backdrop_widget.hpp
|
||||
* @brief MIRAI 框架后效处理控件
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 本文件定义了后效处理控件(backdrop widget)。
|
||||
* 对控件所在区域进行后效处理,如模糊、颜色调整等。
|
||||
* 不能拥有子控件。
|
||||
*
|
||||
* 使用方法:
|
||||
* 1. 编写 GLSL/HLSL 片段着色器,接收 backdrop 纹理作为输入
|
||||
* 2. 使用着色器编译工具生成绑定
|
||||
* 3. 继承 backdrop_widget 并设置着色器绑定
|
||||
*
|
||||
* @code
|
||||
* // 着色器示例 (backdrop_blur.frag)
|
||||
* #version 450
|
||||
* layout(set = 0, binding = 0) uniform sampler2D u_backdrop;
|
||||
* layout(location = 0) in vec2 v_uv;
|
||||
* layout(location = 0) out vec4 fragColor;
|
||||
*
|
||||
* void main() {
|
||||
* vec4 color = texture(sampler2D(u_backdrop, u_sampler), v_uv);
|
||||
* fragColor = color;
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shader_widget.hpp"
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// ================================================================================================
|
||||
// 后效控件基类
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 后效控件基类
|
||||
*
|
||||
* 对控件所在区域应用后效处理的控件。
|
||||
* 使用自定义片段着色器对底层图像进行处理。
|
||||
* 不能拥有子控件。
|
||||
*
|
||||
* 渲染流程:
|
||||
* 1. 渲染完所有子控件后,捕获当前帧缓冲内容到临时纹理
|
||||
* 2. 使用后效着色器渲染到目标
|
||||
*
|
||||
* @tparam T 着色器绑定类型
|
||||
*/
|
||||
template<typename T>
|
||||
class backdrop_widget : public shader_widget<T> {
|
||||
public:
|
||||
using base_type = shader_widget<T>;
|
||||
using bindings_type = T;
|
||||
|
||||
MIRAI_OBJECT_TYPE_INFO(backdrop_widget, shader_widget)
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
backdrop_widget() = default;
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~backdrop_widget() override {
|
||||
destroy_backdrop_resources();
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 渲染参数
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置模糊半径
|
||||
* @param radius 模糊半径
|
||||
*/
|
||||
void set_blur_radius(f32 radius) {
|
||||
blur_radius_ = radius;
|
||||
this->invalidate_pipeline();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取模糊半径
|
||||
* @return 模糊半径
|
||||
*/
|
||||
[[nodiscard]] f32 get_blur_radius() const noexcept { return blur_radius_; }
|
||||
|
||||
/**
|
||||
* @brief 设置强度
|
||||
* @param intensity 强度
|
||||
*/
|
||||
void set_intensity(f32 intensity) {
|
||||
intensity_ = intensity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取强度
|
||||
* @return 强度
|
||||
*/
|
||||
[[nodiscard]] f32 get_intensity() const noexcept { return intensity_; }
|
||||
|
||||
protected:
|
||||
// ============================================================================================
|
||||
// 生命周期
|
||||
// ============================================================================================
|
||||
|
||||
void on_mount() override {
|
||||
base_type::on_mount();
|
||||
create_backdrop_resources();
|
||||
}
|
||||
|
||||
void on_unmount() override {
|
||||
destroy_backdrop_resources();
|
||||
base_type::on_unmount();
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 测量和布局
|
||||
// ============================================================================================
|
||||
|
||||
vec2 on_measure(const size_constraints& constraints) override {
|
||||
// 后效控件通常需要明确的尺寸
|
||||
auto size = base_type::on_measure(constraints);
|
||||
|
||||
// 如果没有明确尺寸,使用约束或默认
|
||||
if (!this->has_explicit_width()) {
|
||||
size.x = constraints.preferred_width.value_or(size.x);
|
||||
size.x = std::min(size.x, constraints.max_width.value_or(f32_MAX));
|
||||
}
|
||||
if (!this->has_explicit_height()) {
|
||||
size.y = constraints.preferred_height.value_or(size.y);
|
||||
size.y = std::min(size.y, constraints.max_height.value_or(f32_MAX));
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 绘制
|
||||
// ============================================================================================
|
||||
|
||||
void on_shader_paint(paint_context& ctx) override {
|
||||
auto* rc = ctx.get_render_context();
|
||||
if (!rc) return;
|
||||
|
||||
auto& renderer = rc->get_renderer_ref();
|
||||
auto device = renderer.get_device();
|
||||
|
||||
// 绑定管线
|
||||
if (this->pipeline_) {
|
||||
rc->bind_pipeline(this->pipeline_);
|
||||
}
|
||||
|
||||
// 设置视口
|
||||
auto bounds = this->get_bounds();
|
||||
rc->set_viewport(0.0f, 0.0f, bounds.width, bounds.height);
|
||||
rc->set_scissor(0, 0, static_cast<u32>(bounds.width), static_cast<u32>(bounds.height));
|
||||
|
||||
// 绑定描述符集(使用 backdrop 纹理)
|
||||
if (this->descriptor_set_) {
|
||||
// 更新描述符集图像信息
|
||||
vk::DescriptorImageInfo image_info{};
|
||||
image_info.imageView = backdrop_image_view_;
|
||||
image_info.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
image_info.sampler = backdrop_sampler_;
|
||||
|
||||
vk::WriteDescriptorSet write{};
|
||||
write.dstSet = this->descriptor_set_;
|
||||
write.dstBinding = 0;
|
||||
write.descriptorCount = 1;
|
||||
write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
|
||||
write.pImageInfo = &image_info;
|
||||
|
||||
device->get_device().updateDescriptorSets(1, &write, 0, nullptr);
|
||||
|
||||
rc->bind_descriptor_sets(vk::PipelineBindPoint::eGraphics,
|
||||
this->pipeline_layout_,
|
||||
0,
|
||||
{this->descriptor_set_});
|
||||
}
|
||||
|
||||
// 设置推送常量
|
||||
if (this->pipeline_layout_) {
|
||||
set_push_constants(*rc);
|
||||
}
|
||||
|
||||
// 绘制
|
||||
rc->draw_fullscreen_quad();
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 资源管理
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 创建后效资源
|
||||
*/
|
||||
void create_backdrop_resources() {
|
||||
auto* rc = this->get_render_context();
|
||||
if (!rc) return;
|
||||
|
||||
auto& renderer = rc->get_renderer_ref();
|
||||
auto device = renderer.get_device();
|
||||
auto allocator = renderer.get_gpu_allocator();
|
||||
|
||||
auto extent = renderer.get_swapchain_extent();
|
||||
|
||||
// 创建临时图像(用于捕获 backdrop)
|
||||
VkImageCreateInfo image_info{};
|
||||
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
image_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_info.extent.width = extent.width;
|
||||
image_info.extent.height = extent.height;
|
||||
image_info.extent.depth = 1;
|
||||
image_info.mipLevels = 1;
|
||||
image_info.arrayLayers = 1;
|
||||
image_info.format = renderer.get_swapchain_format();
|
||||
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
|
||||
VmaAllocationCreateInfo alloc_info{};
|
||||
alloc_info.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||
|
||||
VkImage image;
|
||||
auto result = vmaCreateImage(allocator->get_vma_allocator(), &image_info, &alloc_info,
|
||||
&image, &backdrop_allocation_, nullptr);
|
||||
if (result != VK_SUCCESS) return;
|
||||
|
||||
backdrop_image_ = image;
|
||||
|
||||
// 创建图像视图
|
||||
vk::ImageViewCreateInfo view_info{};
|
||||
view_info.image = backdrop_image_;
|
||||
view_info.viewType = vk::ImageViewType::e2D;
|
||||
view_info.format = renderer.get_swapchain_format();
|
||||
view_info.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
view_info.subresourceRange.baseMipLevel = 0;
|
||||
view_info.subresourceRange.levelCount = 1;
|
||||
view_info.subresourceRange.baseArrayLayer = 0;
|
||||
view_info.subresourceRange.layerCount = 1;
|
||||
|
||||
auto [view_result, view] = device->get_device().createImageView(view_info);
|
||||
if (view_result != vk::Result::eSuccess) return;
|
||||
backdrop_image_view_ = view;
|
||||
|
||||
// 创建采样器
|
||||
vk::SamplerCreateInfo sampler_info{};
|
||||
sampler_info.magFilter = vk::Filter::eLinear;
|
||||
sampler_info.minFilter = vk::Filter::eLinear;
|
||||
sampler_info.addressModeU = vk::SamplerAddressMode::eClampToEdge;
|
||||
sampler_info.addressModeV = vk::SamplerAddressMode::eClampToEdge;
|
||||
sampler_info.addressModeW = vk::SamplerAddressMode::eClampToEdge;
|
||||
sampler_info.borderColor = vk::BorderColor::eFloatTransparentBlack;
|
||||
sampler_info.unnormalizedCoordinates = VK_FALSE;
|
||||
|
||||
auto [sampler_result, sampler] = device->get_device().createSampler(sampler_info);
|
||||
if (sampler_result != vk::Result::eSuccess) return;
|
||||
backdrop_sampler_ = sampler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 销毁后效资源
|
||||
*/
|
||||
void destroy_backdrop_resources() {
|
||||
auto* rc = this->get_render_context();
|
||||
if (!rc) return;
|
||||
|
||||
auto& renderer = rc->get_renderer_ref();
|
||||
auto device = renderer.get_device();
|
||||
auto allocator = renderer.get_gpu_allocator();
|
||||
|
||||
if (backdrop_sampler_) {
|
||||
device->get_device().destroySampler(backdrop_sampler_);
|
||||
backdrop_sampler_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (backdrop_image_view_) {
|
||||
device->get_device().destroyImageView(backdrop_image_view_);
|
||||
backdrop_image_view_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (backdrop_image_ && backdrop_allocation_) {
|
||||
vmaDestroyImage(allocator->get_vma_allocator(), backdrop_image_, backdrop_allocation_);
|
||||
backdrop_image_ = VK_NULL_HANDLE;
|
||||
backdrop_allocation_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置推送常量
|
||||
*/
|
||||
virtual void set_push_constants(render_context& ctx) {
|
||||
// 子类重写以设置特定参数
|
||||
(void)ctx;
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 成员变量
|
||||
// ============================================================================================
|
||||
|
||||
/// 模糊半径
|
||||
f32 blur_radius_ = 5.0f;
|
||||
|
||||
/// 强度
|
||||
f32 intensity_ = 1.0f;
|
||||
|
||||
/// 后效图像
|
||||
vk::Image backdrop_image_ = VK_NULL_HANDLE;
|
||||
|
||||
/// 后效图像分配
|
||||
VmaAllocation backdrop_allocation_ = nullptr;
|
||||
|
||||
/// 后效图像视图
|
||||
vk::ImageView backdrop_image_view_ = VK_NULL_HANDLE;
|
||||
|
||||
/// 后效采样器
|
||||
vk::Sampler backdrop_sampler_ = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// 便捷类型定义
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 创建后效控件
|
||||
*
|
||||
* @tparam T 着色器绑定类型
|
||||
* @return 后效控件指针
|
||||
*/
|
||||
template<typename T>
|
||||
[[nodiscard]] std::shared_ptr<backdrop_widget<T>> make_backdrop_widget() {
|
||||
return std::make_shared<backdrop_widget<T>>();
|
||||
}
|
||||
|
||||
} // namespace mirai
|
||||
291
src/ui/mask_widget.hpp
Normal file
291
src/ui/mask_widget.hpp
Normal file
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* @file mask_widget.hpp
|
||||
* @brief MIRAI 框架遮罩渲染控件
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 本文件定义了遮罩渲染控件(mask widget)。
|
||||
* 使用自定义片段着色器对子控件进行遮罩处理。
|
||||
* 可以拥有子控件。
|
||||
*
|
||||
* 使用方法:
|
||||
* 1. 编写 Slang 片段着色器,使用遮罩纹理和 UV 计算透明度
|
||||
* 2. 使用着色器编译工具生成绑定
|
||||
* 3. 继承 mask_widget 并设置着色器绑定
|
||||
*
|
||||
* @code
|
||||
* 改成slang着色器
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shader_widget.hpp"
|
||||
#include "container.hpp"
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// ================================================================================================
|
||||
// 遮罩渲染控件
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 遮罩渲染控件
|
||||
*
|
||||
* 使用自定义片段着色器对子控件进行遮罩处理的控件。
|
||||
* 可以拥有子控件。
|
||||
*
|
||||
* 渲染流程:
|
||||
* 1. 首先将子控件渲染到临时帧缓冲/纹理
|
||||
* 2. 使用遮罩着色器将子控件内容与遮罩纹理合并
|
||||
*
|
||||
* @tparam T 着色器绑定类型
|
||||
*/
|
||||
template<typename T>
|
||||
class mask_widget : public container {
|
||||
public:
|
||||
using base_type = container;
|
||||
using bindings_type = T;
|
||||
|
||||
MIRAI_OBJECT_TYPE_INFO(mask_widget, container)
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
mask_widget() = default;
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~mask_widget() override {
|
||||
destroy_mask_resources();
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 遮罩参数
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置遮罩纹理
|
||||
* @param texture 遮罩纹理
|
||||
*/
|
||||
void set_mask_texture(std::shared_ptr<texture> texture) {
|
||||
mask_texture_ = std::move(texture);
|
||||
invalidate_pipeline();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取遮罩纹理
|
||||
* @return 遮罩纹理
|
||||
*/
|
||||
[[nodiscard]] std::shared_ptr<texture> get_mask_texture() const noexcept { return mask_texture_; }
|
||||
|
||||
/**
|
||||
* @brief 设置遮罩反相
|
||||
* @param invert 是否反相
|
||||
*/
|
||||
void set_invert_mask(bool invert) {
|
||||
invert_mask_ = invert;
|
||||
invalidate_pipeline();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取遮罩反相
|
||||
* @return 是否反相
|
||||
*/
|
||||
[[nodiscard]] bool get_invert_mask() const noexcept { return invert_mask_; }
|
||||
|
||||
protected:
|
||||
// ============================================================================================
|
||||
// 生命周期
|
||||
// ============================================================================================
|
||||
|
||||
void on_mount() override {
|
||||
base_type::on_mount();
|
||||
create_mask_resources();
|
||||
}
|
||||
|
||||
void on_unmount() override {
|
||||
destroy_mask_resources();
|
||||
base_type::on_unmount();
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 测量和布局
|
||||
// ============================================================================================
|
||||
|
||||
vec2 on_measure(const size_constraints& constraints) override {
|
||||
// 首先测量子控件
|
||||
auto content_size = measure_children(constraints);
|
||||
|
||||
// 使用子控件大小作为自己的大小
|
||||
return content_size;
|
||||
}
|
||||
|
||||
void on_layout(const rect2d& bounds) override {
|
||||
base_type::on_layout(bounds);
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 绘制
|
||||
// ============================================================================================
|
||||
|
||||
void on_paint(paint_context& ctx) override {
|
||||
if (!is_visible()) return;
|
||||
|
||||
// 绘制子控件(将渲染到临时纹理)
|
||||
paint_children(ctx);
|
||||
|
||||
// 使用遮罩着色器渲染
|
||||
paint_with_mask(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使用遮罩绘制
|
||||
* @param ctx 绘制上下文
|
||||
*/
|
||||
virtual void paint_with_mask(paint_context& ctx) {
|
||||
auto* rc = ctx.get_render_context();
|
||||
if (!rc) return;
|
||||
|
||||
// 绑定管线
|
||||
if (mask_pipeline_) {
|
||||
rc->bind_pipeline(mask_pipeline_);
|
||||
}
|
||||
|
||||
// 设置视口
|
||||
auto bounds = get_bounds();
|
||||
rc->set_viewport(0.0f, 0.0f, bounds.width, bounds.height);
|
||||
rc->set_scissor(0, 0, static_cast<u32>(bounds.width), static_cast<u32>(bounds.height));
|
||||
|
||||
// 绑定描述符集
|
||||
if (mask_descriptor_set_) {
|
||||
rc->bind_descriptor_sets(vk::PipelineBindPoint::eGraphics,
|
||||
mask_pipeline_layout_,
|
||||
0,
|
||||
{mask_descriptor_set_});
|
||||
}
|
||||
|
||||
// 设置推送常量
|
||||
if (mask_pipeline_layout_) {
|
||||
set_push_constants(*rc);
|
||||
}
|
||||
|
||||
// 绘制
|
||||
rc->draw_fullscreen_quad();
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 资源管理
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 创建遮罩资源
|
||||
*/
|
||||
void create_mask_resources() {
|
||||
// 子类重写以创建特定资源
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 销毁遮罩资源
|
||||
*/
|
||||
void destroy_mask_resources() {
|
||||
auto* rc = get_render_context();
|
||||
if (!rc) return;
|
||||
|
||||
auto& renderer = rc->get_renderer_ref();
|
||||
auto device = renderer.get_device();
|
||||
|
||||
if (mask_pipeline_) {
|
||||
device->get_device().destroyPipeline(mask_pipeline_);
|
||||
mask_pipeline_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (mask_pipeline_layout_) {
|
||||
device->get_device().destroyPipelineLayout(mask_pipeline_layout_);
|
||||
mask_pipeline_layout_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (mask_descriptor_pool_) {
|
||||
device->get_device().destroyDescriptorPool(mask_descriptor_pool_);
|
||||
mask_descriptor_pool_ = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取渲染上下文
|
||||
* @return 渲染上下文指针
|
||||
*/
|
||||
[[nodiscard]] render_context* get_render_context() const {
|
||||
// 从外部获取渲染上下文
|
||||
// 子类或外部代码需要设置
|
||||
return render_context_.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置渲染上下文
|
||||
* @param ctx 渲染上下文
|
||||
*/
|
||||
void set_render_context(std::unique_ptr<render_context> ctx) {
|
||||
render_context_ = std::move(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 标记管线需要重建
|
||||
*/
|
||||
void invalidate_pipeline() noexcept {
|
||||
mask_pipeline_dirty_ = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置推送常量
|
||||
*/
|
||||
virtual void set_push_constants(render_context& ctx) {
|
||||
(void)ctx;
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 成员变量
|
||||
// ============================================================================================
|
||||
|
||||
/// 渲染上下文
|
||||
std::unique_ptr<render_context> render_context_;
|
||||
|
||||
/// 遮罩纹理
|
||||
std::shared_ptr<texture> mask_texture_;
|
||||
|
||||
/// 是否反相遮罩
|
||||
bool invert_mask_ = false;
|
||||
|
||||
/// 遮罩管线
|
||||
vk::Pipeline mask_pipeline_ = VK_NULL_HANDLE;
|
||||
|
||||
/// 遮罩管线布局
|
||||
vk::PipelineLayout mask_pipeline_layout_ = VK_NULL_HANDLE;
|
||||
|
||||
/// 遮罩描述符池
|
||||
vk::DescriptorPool mask_descriptor_pool_ = VK_NULL_HANDLE;
|
||||
|
||||
/// 遮罩描述符集
|
||||
vk::DescriptorSet mask_descriptor_set_ = VK_NULL_HANDLE;
|
||||
|
||||
/// 管线是否需要重建
|
||||
bool mask_pipeline_dirty_ = true;
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// 便捷类型定义
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 创建遮罩控件
|
||||
*
|
||||
* @tparam T 着色器绑定类型
|
||||
* @return 遮罩控件指针
|
||||
*/
|
||||
template<typename T>
|
||||
[[nodiscard]] std::shared_ptr<mask_widget<T>> make_mask_widget() {
|
||||
return std::make_shared<mask_widget<T>>();
|
||||
}
|
||||
|
||||
} // namespace mirai
|
||||
@@ -670,6 +670,18 @@ public:
|
||||
*/
|
||||
[[nodiscard]] text_renderer* get_text_renderer() const noexcept { return text_renderer_; }
|
||||
|
||||
/**
|
||||
* @brief 获取渲染上下文
|
||||
* @return 渲染上下文指针,如果不可用返回 nullptr
|
||||
*/
|
||||
[[nodiscard]] class render_context* get_render_context() const noexcept { return render_context_; }
|
||||
|
||||
/**
|
||||
* @brief 设置渲染上下文
|
||||
* @param ctx 渲染上下文
|
||||
*/
|
||||
void set_render_context(class render_context* ctx) noexcept { render_context_ = ctx; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 获取当前状态
|
||||
@@ -705,6 +717,9 @@ private:
|
||||
/// 文本渲染器
|
||||
text_renderer* text_renderer_ = nullptr;
|
||||
|
||||
/// 渲染上下文
|
||||
class render_context* render_context_ = nullptr;
|
||||
|
||||
/// 状态栈
|
||||
std::stack<paint_state> state_stack_;
|
||||
};
|
||||
|
||||
25
src/widget/CMakeLists.txt
Normal file
25
src/widget/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
# ================================================================================================
|
||||
# MIRAI Framework - Widget 模块
|
||||
# 描述: shader_widget 基础类和控件系统
|
||||
# ================================================================================================
|
||||
|
||||
project(mirai_widget)
|
||||
simple_library(STATIC)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC
|
||||
mirai_ui
|
||||
mirai_shader
|
||||
)
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR}
|
||||
EXPORT_NAME widget
|
||||
OUTPUT_NAME mirai_widget
|
||||
)
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT mirai-targets
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
message(STATUS "Configured mirai_widget library")
|
||||
230
src/widget/effects.hpp
Normal file
230
src/widget/effects.hpp
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* @file effects.hpp
|
||||
* @brief MIRAI 框架效果控件汇总头文件
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 本文件提供所有效果控件的统一包含入口,方便用户一次性包含所有效果。
|
||||
*
|
||||
* 包含的效果控件:
|
||||
* - blur_widget: 高斯模糊效果(Backdrop 模式)
|
||||
* - gradient_widget: 渐变效果(Procedural 模式)
|
||||
* - rounded_rect_mask_widget: 圆角矩形遮罩(Mask 模式)
|
||||
* - particle_widget: 粒子系统(Mesh 模式)
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* #include "widget/effects.hpp"
|
||||
*
|
||||
* using namespace mirai;
|
||||
*
|
||||
* // 创建各种效果控件
|
||||
* auto blur = std::make_shared<blur_widget>();
|
||||
* auto gradient = std::make_shared<gradient_widget>();
|
||||
* auto mask = std::make_shared<rounded_rect_mask_widget>();
|
||||
* auto particles = std::make_shared<particle_widget>(1000);
|
||||
* @endcode
|
||||
*
|
||||
* @see shader_widget_base
|
||||
* @see shader_widget
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// ============================================================================
|
||||
// 基础类型和基类
|
||||
// ============================================================================
|
||||
|
||||
#include "widget_types.hpp"
|
||||
#include "shader_widget_base.hpp"
|
||||
#include "shader_widget.hpp"
|
||||
|
||||
// ============================================================================
|
||||
// Backdrop 模式效果
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @defgroup backdrop_effects Backdrop 模式效果
|
||||
* @brief 后效处理效果,可采样背景纹理进行处理
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include "effects/blur_widget.hpp"
|
||||
|
||||
/** @} */
|
||||
|
||||
// ============================================================================
|
||||
// Procedural 模式效果
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @defgroup procedural_effects Procedural 模式效果
|
||||
* @brief 程序化渲染效果,基于时间和坐标生成图形
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include "effects/gradient_widget.hpp"
|
||||
|
||||
/** @} */
|
||||
|
||||
// ============================================================================
|
||||
// Mask 模式效果
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @defgroup mask_effects Mask 模式效果
|
||||
* @brief 遮罩效果,用于裁剪和形状遮罩
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include "effects/rounded_rect_mask_widget.hpp"
|
||||
|
||||
/** @} */
|
||||
|
||||
// ============================================================================
|
||||
// Mesh 模式效果
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @defgroup mesh_effects Mesh 模式效果
|
||||
* @brief 自定义网格渲染效果,支持实例化渲染
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include "effects/particle_widget.hpp"
|
||||
|
||||
/** @} */
|
||||
|
||||
// ============================================================================
|
||||
// 便捷类型别名
|
||||
// ============================================================================
|
||||
|
||||
namespace mirai {
|
||||
|
||||
/**
|
||||
* @brief 效果控件类型别名
|
||||
* @{
|
||||
*/
|
||||
|
||||
/// 模糊效果控件指针类型
|
||||
using blur_widget_ptr = std::shared_ptr<blur_widget>;
|
||||
|
||||
/// 渐变效果控件指针类型
|
||||
using gradient_widget_ptr = std::shared_ptr<gradient_widget>;
|
||||
|
||||
/// 圆角遮罩控件指针类型
|
||||
using rounded_rect_mask_widget_ptr = std::shared_ptr<rounded_rect_mask_widget>;
|
||||
|
||||
/// 粒子系统控件指针类型
|
||||
using particle_widget_ptr = std::shared_ptr<particle_widget>;
|
||||
|
||||
/** @} */
|
||||
|
||||
// ============================================================================
|
||||
// 工厂函数
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 效果控件工厂函数
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief 创建模糊效果控件
|
||||
* @param radius 模糊半径(像素),默认 8.0f
|
||||
* @param quality 模糊质量,默认 medium
|
||||
* @return 模糊效果控件指针
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto blur = make_blur_widget(10.0f, blur_quality::high);
|
||||
* @endcode
|
||||
*/
|
||||
[[nodiscard]] inline blur_widget_ptr make_blur_widget(
|
||||
f32 radius = 8.0f,
|
||||
blur_quality quality = blur_quality::medium
|
||||
) {
|
||||
return std::make_shared<blur_widget>(radius, quality);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建渐变效果控件
|
||||
* @param start 起始颜色
|
||||
* @param end 结束颜色
|
||||
* @param type 渐变类型,默认 linear
|
||||
* @return 渐变效果控件指针
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto gradient = make_gradient_widget(
|
||||
* color{1.0f, 0.0f, 0.0f, 1.0f},
|
||||
* color{0.0f, 0.0f, 1.0f, 1.0f},
|
||||
* gradient_type::radial
|
||||
* );
|
||||
* @endcode
|
||||
*/
|
||||
[[nodiscard]] inline gradient_widget_ptr make_gradient_widget(
|
||||
const color& start = color{1.0f, 1.0f, 1.0f, 1.0f},
|
||||
const color& end = color{0.0f, 0.0f, 0.0f, 1.0f},
|
||||
gradient_type type = gradient_type::linear
|
||||
) {
|
||||
return std::make_shared<gradient_widget>(start, end, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建圆角遮罩控件
|
||||
* @param radius 统一圆角半径,默认 0.0f
|
||||
* @return 圆角遮罩控件指针
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto mask = make_rounded_rect_mask_widget(0.1f);
|
||||
* @endcode
|
||||
*/
|
||||
[[nodiscard]] inline rounded_rect_mask_widget_ptr make_rounded_rect_mask_widget(
|
||||
f32 radius = 0.0f
|
||||
) {
|
||||
return std::make_shared<rounded_rect_mask_widget>(radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建圆角遮罩控件(四角独立)
|
||||
* @param top_left 左上角圆角半径
|
||||
* @param top_right 右上角圆角半径
|
||||
* @param bottom_right 右下角圆角半径
|
||||
* @param bottom_left 左下角圆角半径
|
||||
* @return 圆角遮罩控件指针
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto mask = make_rounded_rect_mask_widget(0.1f, 0.1f, 0.2f, 0.2f);
|
||||
* @endcode
|
||||
*/
|
||||
[[nodiscard]] inline rounded_rect_mask_widget_ptr make_rounded_rect_mask_widget(
|
||||
f32 top_left,
|
||||
f32 top_right,
|
||||
f32 bottom_right,
|
||||
f32 bottom_left
|
||||
) {
|
||||
return std::make_shared<rounded_rect_mask_widget>(
|
||||
top_left, top_right, bottom_right, bottom_left
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建粒子系统控件
|
||||
* @param count 粒子数量,默认 100
|
||||
* @return 粒子系统控件指针
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto particles = make_particle_widget(1000);
|
||||
* @endcode
|
||||
*/
|
||||
[[nodiscard]] inline particle_widget_ptr make_particle_widget(u32 count = 100) {
|
||||
return std::make_shared<particle_widget>(count);
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
} // namespace mirai
|
||||
164
src/widget/effects/blur_widget.hpp
Normal file
164
src/widget/effects/blur_widget.hpp
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* @file blur_widget.hpp
|
||||
* @brief 高斯模糊效果控件
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 本文件定义了 blur_widget 类,实现 Backdrop 模式的高斯模糊效果。
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto blur = std::make_shared<blur_widget>();
|
||||
* blur->set_blur_radius(10.0f);
|
||||
* blur->set_blur_quality(blur_quality::medium);
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shader_widget.hpp"
|
||||
#include ""
|
||||
|
||||
#include <span>
|
||||
#include <cstdint>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// ================================================================================================
|
||||
// 模糊质量枚举
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 模糊质量等级
|
||||
*/
|
||||
enum class blur_quality : u8 {
|
||||
low = 1, ///< 低质量(4 次采样)
|
||||
medium = 2, ///< 中等质量(8 次采样)
|
||||
high = 3 ///< 高质量(12 次采样)
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// blur_widget 类
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 高斯模糊效果控件
|
||||
*
|
||||
* 继承自 backdrop_widget,实现对背景的高斯模糊效果。
|
||||
*
|
||||
* 特点:
|
||||
* - 可调节的模糊半径
|
||||
* - 三档质量选择(低/中/高)
|
||||
* - 使用分离式高斯模糊算法
|
||||
*
|
||||
* @see backdrop_widget
|
||||
* @see blur_bindings
|
||||
*/
|
||||
class blur_widget : public backdrop_widget<blur_bindings> {
|
||||
public:
|
||||
MIRAI_OBJECT_TYPE_INFO(blur_widget, backdrop_widget<blur_bindings>)
|
||||
|
||||
// ============================================================================================
|
||||
// 构造与析构
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*
|
||||
* 初始化默认参数:
|
||||
* - blur_radius = 8.0f
|
||||
* - quality = medium
|
||||
*/
|
||||
blur_widget()
|
||||
: blur_radius_(8.0f)
|
||||
, quality_(blur_quality::medium) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 带参数的构造函数
|
||||
* @param radius 模糊半径(像素)
|
||||
* @param quality 模糊质量
|
||||
*/
|
||||
explicit blur_widget(f32 radius, blur_quality quality = blur_quality::medium)
|
||||
: blur_radius_(radius)
|
||||
, quality_(quality) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 虚析构函数
|
||||
*/
|
||||
~blur_widget() override = default;
|
||||
|
||||
// ============================================================================================
|
||||
// 参数设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置模糊半径
|
||||
* @param radius 模糊半径(像素),范围 [0, 100]
|
||||
*/
|
||||
void set_blur_radius(f32 radius) {
|
||||
blur_radius_ = std::clamp(radius, 0.0f, 100.0f);
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取模糊半径
|
||||
* @return 当前模糊半径
|
||||
*/
|
||||
[[nodiscard]] f32 blur_radius() const noexcept { return blur_radius_; }
|
||||
|
||||
/**
|
||||
* @brief 设置模糊质量
|
||||
* @param quality 模糊质量等级
|
||||
*/
|
||||
void set_blur_quality(blur_quality quality) {
|
||||
quality_ = quality;
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取模糊质量
|
||||
* @return 当前模糊质量
|
||||
*/
|
||||
[[nodiscard]] blur_quality quality() const noexcept { return quality_; }
|
||||
|
||||
protected:
|
||||
// ============================================================================================
|
||||
// 重写基类方法
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 更新 Uniform Buffer
|
||||
*
|
||||
* 将模糊参数写入 GPU 缓冲区。
|
||||
*/
|
||||
void update_uniform_buffer() override {
|
||||
// BlurParams 结构体布局(与 blur.slang 中定义一致)
|
||||
struct BlurParams {
|
||||
f32 blur_radius;
|
||||
f32 blur_quality;
|
||||
f32 _padding[2];
|
||||
};
|
||||
|
||||
BlurParams params{};
|
||||
params.blur_radius = blur_radius_;
|
||||
params.blur_quality = static_cast<f32>(quality_);
|
||||
|
||||
// TODO: 将 params 写入 uniform buffer
|
||||
// uniform_buffer_->write(¶ms, sizeof(params));
|
||||
}
|
||||
|
||||
private:
|
||||
// ============================================================================================
|
||||
// 成员变量
|
||||
// ============================================================================================
|
||||
|
||||
/// 模糊半径(像素)
|
||||
f32 blur_radius_;
|
||||
|
||||
/// 模糊质量
|
||||
blur_quality quality_;
|
||||
};
|
||||
|
||||
} // namespace mirai
|
||||
372
src/widget/effects/gradient_widget.hpp
Normal file
372
src/widget/effects/gradient_widget.hpp
Normal file
@@ -0,0 +1,372 @@
|
||||
/**
|
||||
* @file gradient_widget.hpp
|
||||
* @brief 渐变效果控件
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 本文件定义了 gradient_widget 类,实现 Procedural 模式的渐变效果。
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto gradient = std::make_shared<gradient_widget>();
|
||||
* gradient->set_start_color(color::red());
|
||||
* gradient->set_end_color(color::blue());
|
||||
* gradient->set_gradient_type(gradient_type::radial);
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shader_widget.hpp"
|
||||
#include "types/color.h"
|
||||
|
||||
#include <span>
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// ================================================================================================
|
||||
// 渐变类型枚举
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 渐变类型
|
||||
*/
|
||||
enum class gradient_type : u32 {
|
||||
linear = 0, ///< 线性渐变
|
||||
radial = 1, ///< 径向渐变
|
||||
angular = 2, ///< 角度渐变(圆锥渐变)
|
||||
diamond = 3 ///< 菱形渐变
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// Gradient 绑定类型(模拟代码生成器输出)
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief gradient_widget 的着色器绑定类型
|
||||
*
|
||||
* 此类型模拟代码生成器的输出,提供:
|
||||
* - SPIR-V 字节码访问
|
||||
* - 绑定点常量
|
||||
* - 静态反射信息
|
||||
*
|
||||
* @note 在实际项目中,此类型由 shader_codegen 工具自动生成
|
||||
*/
|
||||
struct gradient_bindings {
|
||||
// ============================================================================================
|
||||
// 着色器存在性标记
|
||||
// ============================================================================================
|
||||
|
||||
static constexpr bool HAS_VERTEX_SHADER = true;
|
||||
static constexpr bool HAS_FRAGMENT_SHADER = true;
|
||||
|
||||
// ============================================================================================
|
||||
// Procedural 模式特有标记
|
||||
// ============================================================================================
|
||||
|
||||
static constexpr bool HAS_FRAME_DATA = true;
|
||||
static constexpr u32 FRAME_DATA_BINDING = 0;
|
||||
static constexpr u32 FRAME_DATA_SET = 0;
|
||||
|
||||
// ============================================================================================
|
||||
// SPIR-V 数据访问(占位实现)
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取顶点着色器 SPIR-V 数据
|
||||
* @return SPIR-V 字节码的 span
|
||||
* @note 实际实现中,此数据由编译后的着色器提供
|
||||
*/
|
||||
[[nodiscard]] static std::span<const u32> get_vertex_spirv() {
|
||||
// 占位:实际数据由着色器编译生成
|
||||
static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number
|
||||
return std::span<const u32>(placeholder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取片段着色器 SPIR-V 数据
|
||||
* @return SPIR-V 字节码的 span
|
||||
* @note 实际实现中,此数据由编译后的着色器提供
|
||||
*/
|
||||
[[nodiscard]] static std::span<const u32> get_fragment_spirv() {
|
||||
// 占位:实际数据由着色器编译生成
|
||||
static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number
|
||||
return std::span<const u32>(placeholder);
|
||||
}
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// gradient_widget 类
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 渐变效果控件
|
||||
*
|
||||
* 继承自 procedural_widget,实现程序化渐变效果。
|
||||
*
|
||||
* 特点:
|
||||
* - 支持四种渐变类型(线性、径向、角度、菱形)
|
||||
* - 可自定义起始和结束颜色
|
||||
* - 支持渐变角度和中心点设置
|
||||
* - 支持重复模式
|
||||
*
|
||||
* @see procedural_widget
|
||||
* @see gradient_bindings
|
||||
*/
|
||||
class gradient_widget : public procedural_widget<gradient_bindings> {
|
||||
public:
|
||||
MIRAI_OBJECT_TYPE_INFO(gradient_widget, procedural_widget<gradient_bindings>)
|
||||
|
||||
// ============================================================================================
|
||||
// 构造与析构
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*
|
||||
* 初始化默认参数:
|
||||
* - start_color = 白色
|
||||
* - end_color = 黑色
|
||||
* - type = linear
|
||||
* - angle = 0 (水平)
|
||||
*/
|
||||
gradient_widget()
|
||||
: start_color_{1.0f, 1.0f, 1.0f, 1.0f}
|
||||
, end_color_{0.0f, 0.0f, 0.0f, 1.0f}
|
||||
, type_(gradient_type::linear)
|
||||
, angle_(0.0f)
|
||||
, center_{0.5f, 0.5f}
|
||||
, radius_(0.5f)
|
||||
, repeat_count_(0.0f) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 带颜色参数的构造函数
|
||||
* @param start 起始颜色
|
||||
* @param end 结束颜色
|
||||
* @param type 渐变类型
|
||||
*/
|
||||
gradient_widget(const color& start, const color& end, gradient_type type = gradient_type::linear)
|
||||
: start_color_(start)
|
||||
, end_color_(end)
|
||||
, type_(type)
|
||||
, angle_(0.0f)
|
||||
, center_{0.5f, 0.5f}
|
||||
, radius_(0.5f)
|
||||
, repeat_count_(0.0f) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 虚析构函数
|
||||
*/
|
||||
~gradient_widget() override = default;
|
||||
|
||||
// ============================================================================================
|
||||
// 颜色设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置起始颜色
|
||||
* @param c 起始颜色
|
||||
*/
|
||||
void set_start_color(const color& c) {
|
||||
start_color_ = c;
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取起始颜色
|
||||
* @return 当前起始颜色
|
||||
*/
|
||||
[[nodiscard]] const color& start_color() const noexcept { return start_color_; }
|
||||
|
||||
/**
|
||||
* @brief 设置结束颜色
|
||||
* @param c 结束颜色
|
||||
*/
|
||||
void set_end_color(const color& c) {
|
||||
end_color_ = c;
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取结束颜色
|
||||
* @return 当前结束颜色
|
||||
*/
|
||||
[[nodiscard]] const color& end_color() const noexcept { return end_color_; }
|
||||
|
||||
/**
|
||||
* @brief 同时设置起始和结束颜色
|
||||
* @param start 起始颜色
|
||||
* @param end 结束颜色
|
||||
*/
|
||||
void set_colors(const color& start, const color& end) {
|
||||
start_color_ = start;
|
||||
end_color_ = end;
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 渐变类型设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置渐变类型
|
||||
* @param type 渐变类型
|
||||
*/
|
||||
void set_gradient_type(gradient_type type) {
|
||||
type_ = type;
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取渐变类型
|
||||
* @return 当前渐变类型
|
||||
*/
|
||||
[[nodiscard]] gradient_type type() const noexcept { return type_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 渐变参数设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置渐变角度(用于线性渐变)
|
||||
* @param angle_degrees 角度(度数),0 = 水平向右
|
||||
*/
|
||||
void set_angle(f32 angle_degrees) {
|
||||
angle_ = angle_degrees * (3.14159265358979323846f / 180.0f);
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取渐变角度
|
||||
* @return 当前角度(弧度)
|
||||
*/
|
||||
[[nodiscard]] f32 angle() const noexcept { return angle_; }
|
||||
|
||||
/**
|
||||
* @brief 设置渐变中心(用于径向/角度渐变)
|
||||
* @param x 中心 X 坐标 (0-1)
|
||||
* @param y 中心 Y 坐标 (0-1)
|
||||
*/
|
||||
void set_center(f32 x, f32 y) {
|
||||
center_[0] = x;
|
||||
center_[1] = y;
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取渐变中心 X 坐标
|
||||
* @return 中心 X 坐标
|
||||
*/
|
||||
[[nodiscard]] f32 center_x() const noexcept { return center_[0]; }
|
||||
|
||||
/**
|
||||
* @brief 获取渐变中心 Y 坐标
|
||||
* @return 中心 Y 坐标
|
||||
*/
|
||||
[[nodiscard]] f32 center_y() const noexcept { return center_[1]; }
|
||||
|
||||
/**
|
||||
* @brief 设置渐变半径(用于径向渐变)
|
||||
* @param r 半径 (0-1)
|
||||
*/
|
||||
void set_radius(f32 r) {
|
||||
radius_ = std::max(0.001f, r);
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取渐变半径
|
||||
* @return 当前半径
|
||||
*/
|
||||
[[nodiscard]] f32 radius() const noexcept { return radius_; }
|
||||
|
||||
/**
|
||||
* @brief 设置重复次数
|
||||
* @param count 重复次数(0 = 不重复)
|
||||
*/
|
||||
void set_repeat_count(f32 count) {
|
||||
repeat_count_ = std::max(0.0f, count);
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取重复次数
|
||||
* @return 当前重复次数
|
||||
*/
|
||||
[[nodiscard]] f32 repeat_count() const noexcept { return repeat_count_; }
|
||||
|
||||
protected:
|
||||
// ============================================================================================
|
||||
// 重写基类方法
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 更新 Uniform Buffer
|
||||
*
|
||||
* 将渐变参数写入 GPU 缓冲区。
|
||||
*/
|
||||
void update_uniform_buffer() override {
|
||||
// GradientParams 结构体布局(与 gradient.slang 中定义一致)
|
||||
struct GradientParams {
|
||||
f32 start_color[4];
|
||||
f32 end_color[4];
|
||||
f32 angle;
|
||||
u32 gradient_type;
|
||||
f32 center[2];
|
||||
f32 radius;
|
||||
f32 repeat_count;
|
||||
f32 _padding[2];
|
||||
};
|
||||
|
||||
GradientParams params{};
|
||||
params.start_color[0] = start_color_.r;
|
||||
params.start_color[1] = start_color_.g;
|
||||
params.start_color[2] = start_color_.b;
|
||||
params.start_color[3] = start_color_.a;
|
||||
params.end_color[0] = end_color_.r;
|
||||
params.end_color[1] = end_color_.g;
|
||||
params.end_color[2] = end_color_.b;
|
||||
params.end_color[3] = end_color_.a;
|
||||
params.angle = angle_;
|
||||
params.gradient_type = static_cast<u32>(type_);
|
||||
params.center[0] = center_[0];
|
||||
params.center[1] = center_[1];
|
||||
params.radius = radius_;
|
||||
params.repeat_count = repeat_count_;
|
||||
|
||||
// TODO: 将 params 写入 uniform buffer
|
||||
// uniform_buffer_->write(¶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
|
||||
485
src/widget/effects/particle_widget.hpp
Normal file
485
src/widget/effects/particle_widget.hpp
Normal file
@@ -0,0 +1,485 @@
|
||||
/**
|
||||
* @file particle_widget.hpp
|
||||
* @brief 粒子系统控件
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 本文件定义了 particle_widget 类,实现 Mesh 模式的粒子渲染效果。
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto particles = std::make_shared<particle_widget>();
|
||||
* particles->set_particle_count(1000);
|
||||
* particles->set_particle_size(5.0f, 1.0f); // 起始大小 -> 结束大小
|
||||
* particles->set_colors(color::yellow(), color::red());
|
||||
* particles->set_particle_mode(particle_mode::billboard);
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shader_widget.hpp"
|
||||
#include "types/color.h"
|
||||
|
||||
#include <span>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// ================================================================================================
|
||||
// 粒子渲染模式枚举
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 粒子渲染模式
|
||||
*/
|
||||
enum class particle_mode : u32 {
|
||||
billboard = 0, ///< Billboard 模式(始终面向相机)
|
||||
velocity_aligned = 1, ///< 速度对齐模式
|
||||
stretched = 2 ///< 拉伸模式(沿速度方向拉伸)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 粒子形状
|
||||
*/
|
||||
enum class particle_shape : u8 {
|
||||
textured, ///< 使用纹理
|
||||
circle, ///< 圆形(无纹理)
|
||||
soft, ///< 软边缘圆形
|
||||
glow ///< 发光效果
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// 粒子实例数据
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 单个粒子的实例数据
|
||||
*
|
||||
* 与 mesh.slang 中的 ParticleInstance 结构体对应
|
||||
*/
|
||||
struct particle_instance {
|
||||
f32 position[3]; ///< 粒子位置
|
||||
f32 size; ///< 粒子大小
|
||||
f32 color[4]; ///< 粒子颜色
|
||||
f32 rotation; ///< 粒子旋转
|
||||
f32 life; ///< 生命周期 (0-1)
|
||||
f32 velocity[2]; ///< 速度
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// Particle 绑定类型(模拟代码生成器输出)
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief particle_widget 的着色器绑定类型
|
||||
*
|
||||
* 此类型模拟代码生成器的输出,提供:
|
||||
* - SPIR-V 字节码访问
|
||||
* - 绑定点常量
|
||||
* - 顶点类型定义
|
||||
*
|
||||
* @note 在实际项目中,此类型由 shader_codegen 工具自动生成
|
||||
*/
|
||||
struct particle_bindings {
|
||||
// ============================================================================================
|
||||
// 着色器存在性标记
|
||||
// ============================================================================================
|
||||
|
||||
static constexpr bool HAS_VERTEX_SHADER = true;
|
||||
static constexpr bool HAS_FRAGMENT_SHADER = true;
|
||||
|
||||
// ============================================================================================
|
||||
// Mesh 模式特有标记
|
||||
// ============================================================================================
|
||||
|
||||
static constexpr bool HAS_CUSTOM_VERTEX_INPUT = true;
|
||||
|
||||
/// 顶点类型定义
|
||||
using VertexType = particle_instance;
|
||||
|
||||
// ============================================================================================
|
||||
// SPIR-V 数据访问(占位实现)
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取顶点着色器 SPIR-V 数据
|
||||
* @return SPIR-V 字节码的 span
|
||||
* @note 实际实现中,此数据由编译后的着色器提供
|
||||
*/
|
||||
[[nodiscard]] static std::span<const u32> get_vertex_spirv() {
|
||||
// 占位:实际数据由着色器编译生成
|
||||
static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number
|
||||
return std::span<const u32>(placeholder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取片段着色器 SPIR-V 数据
|
||||
* @return SPIR-V 字节码的 span
|
||||
* @note 实际实现中,此数据由编译后的着色器提供
|
||||
*/
|
||||
[[nodiscard]] static std::span<const u32> get_fragment_spirv() {
|
||||
// 占位:实际数据由着色器编译生成
|
||||
static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number
|
||||
return std::span<const u32>(placeholder);
|
||||
}
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// particle_widget 类
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 粒子系统控件
|
||||
*
|
||||
* 继承自 mesh_widget,使用实例化渲染绘制大量粒子。
|
||||
*
|
||||
* 特点:
|
||||
* - 支持大量粒子的高效渲染
|
||||
* - 多种渲染模式(Billboard、速度对齐、拉伸)
|
||||
* - 生命周期颜色和大小动画
|
||||
* - 淡入淡出效果
|
||||
*
|
||||
* @see mesh_widget
|
||||
* @see particle_bindings
|
||||
*/
|
||||
class particle_widget : public mesh_widget<particle_bindings> {
|
||||
public:
|
||||
MIRAI_OBJECT_TYPE_INFO(particle_widget, mesh_widget<particle_bindings>)
|
||||
|
||||
// ============================================================================================
|
||||
// 构造与析构
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*
|
||||
* 初始化默认参数:
|
||||
* - particle_count = 100
|
||||
* - size_start = 10.0f
|
||||
* - size_end = 2.0f
|
||||
* - color_start = 白色
|
||||
* - color_end = 透明白色
|
||||
*/
|
||||
particle_widget()
|
||||
: particle_count_(100)
|
||||
, size_start_(10.0f)
|
||||
, size_end_(2.0f)
|
||||
, color_start_{1.0f, 1.0f, 1.0f, 1.0f}
|
||||
, color_end_{1.0f, 1.0f, 1.0f, 0.0f}
|
||||
, fade_in_(0.1f)
|
||||
, fade_out_(0.3f)
|
||||
, mode_(particle_mode::billboard)
|
||||
, shape_(particle_shape::circle)
|
||||
, stretch_factor_(0.5f) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 带粒子数量的构造函数
|
||||
* @param count 粒子数量
|
||||
*/
|
||||
explicit particle_widget(u32 count)
|
||||
: particle_count_(count)
|
||||
, size_start_(10.0f)
|
||||
, size_end_(2.0f)
|
||||
, color_start_{1.0f, 1.0f, 1.0f, 1.0f}
|
||||
, color_end_{1.0f, 1.0f, 1.0f, 0.0f}
|
||||
, fade_in_(0.1f)
|
||||
, fade_out_(0.3f)
|
||||
, mode_(particle_mode::billboard)
|
||||
, shape_(particle_shape::circle)
|
||||
, stretch_factor_(0.5f) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 虚析构函数
|
||||
*/
|
||||
~particle_widget() override = default;
|
||||
|
||||
// ============================================================================================
|
||||
// 粒子数量设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置粒子数量
|
||||
* @param count 粒子数量
|
||||
*/
|
||||
void set_particle_count(u32 count) {
|
||||
particle_count_ = count;
|
||||
particles_.resize(count);
|
||||
mark_dirty(widget_dirty_flags::vertex_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取粒子数量
|
||||
* @return 当前粒子数量
|
||||
*/
|
||||
[[nodiscard]] u32 particle_count() const noexcept { return particle_count_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 大小设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置粒子大小范围
|
||||
* @param start 起始大小(刚出生时)
|
||||
* @param end 结束大小(死亡时)
|
||||
*/
|
||||
void set_particle_size(f32 start, f32 end) {
|
||||
size_start_ = std::max(0.0f, start);
|
||||
size_end_ = std::max(0.0f, end);
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置统一粒子大小
|
||||
* @param size 粒子大小
|
||||
*/
|
||||
void set_particle_size(f32 size) {
|
||||
set_particle_size(size, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取起始大小
|
||||
* @return 起始大小
|
||||
*/
|
||||
[[nodiscard]] f32 size_start() const noexcept { return size_start_; }
|
||||
|
||||
/**
|
||||
* @brief 获取结束大小
|
||||
* @return 结束大小
|
||||
*/
|
||||
[[nodiscard]] f32 size_end() const noexcept { return size_end_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 颜色设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置粒子颜色范围
|
||||
* @param start 起始颜色(刚出生时)
|
||||
* @param end 结束颜色(死亡时)
|
||||
*/
|
||||
void set_colors(const color& start, const color& end) {
|
||||
color_start_ = start;
|
||||
color_end_ = end;
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置起始颜色
|
||||
* @param c 起始颜色
|
||||
*/
|
||||
void set_color_start(const color& c) {
|
||||
color_start_ = c;
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置结束颜色
|
||||
* @param c 结束颜色
|
||||
*/
|
||||
void set_color_end(const color& c) {
|
||||
color_end_ = c;
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取起始颜色
|
||||
* @return 起始颜色
|
||||
*/
|
||||
[[nodiscard]] const color& color_start() const noexcept { return color_start_; }
|
||||
|
||||
/**
|
||||
* @brief 获取结束颜色
|
||||
* @return 结束颜色
|
||||
*/
|
||||
[[nodiscard]] const color& color_end() const noexcept { return color_end_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 淡入淡出设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置淡入淡出时间
|
||||
* @param fade_in 淡入时间比例 (0-1)
|
||||
* @param fade_out 淡出时间比例 (0-1)
|
||||
*/
|
||||
void set_fade(f32 fade_in, f32 fade_out) {
|
||||
fade_in_ = std::clamp(fade_in, 0.0f, 1.0f);
|
||||
fade_out_ = std::clamp(fade_out, 0.0f, 1.0f);
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取淡入时间
|
||||
* @return 淡入时间比例
|
||||
*/
|
||||
[[nodiscard]] f32 fade_in() const noexcept { return fade_in_; }
|
||||
|
||||
/**
|
||||
* @brief 获取淡出时间
|
||||
* @return 淡出时间比例
|
||||
*/
|
||||
[[nodiscard]] f32 fade_out() const noexcept { return fade_out_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 渲染模式设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置粒子渲染模式
|
||||
* @param mode 渲染模式
|
||||
*/
|
||||
void set_particle_mode(particle_mode mode) {
|
||||
mode_ = mode;
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取粒子渲染模式
|
||||
* @return 当前渲染模式
|
||||
*/
|
||||
[[nodiscard]] particle_mode mode() const noexcept { return mode_; }
|
||||
|
||||
/**
|
||||
* @brief 设置粒子形状
|
||||
* @param shape 粒子形状
|
||||
*/
|
||||
void set_particle_shape(particle_shape shape) {
|
||||
shape_ = shape;
|
||||
mark_dirty(widget_dirty_flags::pipeline);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取粒子形状
|
||||
* @return 当前粒子形状
|
||||
*/
|
||||
[[nodiscard]] particle_shape shape() const noexcept { return shape_; }
|
||||
|
||||
/**
|
||||
* @brief 设置拉伸因子
|
||||
* @param factor 拉伸因子(用于速度拉伸模式)
|
||||
*/
|
||||
void set_stretch_factor(f32 factor) {
|
||||
stretch_factor_ = std::max(0.0f, factor);
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取拉伸因子
|
||||
* @return 当前拉伸因子
|
||||
*/
|
||||
[[nodiscard]] f32 stretch_factor() const noexcept { return stretch_factor_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 粒子数据访问
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取粒子数据(可修改)
|
||||
* @return 粒子数据向量的引用
|
||||
*/
|
||||
[[nodiscard]] std::vector<particle_instance>& particles() { return particles_; }
|
||||
|
||||
/**
|
||||
* @brief 获取粒子数据(只读)
|
||||
* @return 粒子数据向量的常量引用
|
||||
*/
|
||||
[[nodiscard]] const std::vector<particle_instance>& particles() const { return particles_; }
|
||||
|
||||
/**
|
||||
* @brief 标记粒子数据已更新
|
||||
*
|
||||
* 在修改粒子数据后调用此方法以触发 GPU 缓冲区更新
|
||||
*/
|
||||
void mark_particles_dirty() {
|
||||
mark_dirty(widget_dirty_flags::vertex_buffer);
|
||||
}
|
||||
|
||||
protected:
|
||||
// ============================================================================================
|
||||
// 重写基类方法
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 更新 Uniform Buffer
|
||||
*
|
||||
* 将粒子系统参数写入 GPU 缓冲区。
|
||||
*/
|
||||
void update_uniform_buffer() override {
|
||||
// ParticleSystemParams 结构体布局(与 particle.slang 中定义一致)
|
||||
struct ParticleSystemParams {
|
||||
f32 color_start[4];
|
||||
f32 color_end[4];
|
||||
f32 size_start;
|
||||
f32 size_end;
|
||||
f32 fade_in;
|
||||
f32 fade_out;
|
||||
u32 particle_mode;
|
||||
f32 stretch_factor;
|
||||
f32 _padding[2];
|
||||
};
|
||||
|
||||
ParticleSystemParams params{};
|
||||
params.color_start[0] = color_start_.r;
|
||||
params.color_start[1] = color_start_.g;
|
||||
params.color_start[2] = color_start_.b;
|
||||
params.color_start[3] = color_start_.a;
|
||||
params.color_end[0] = color_end_.r;
|
||||
params.color_end[1] = color_end_.g;
|
||||
params.color_end[2] = color_end_.b;
|
||||
params.color_end[3] = color_end_.a;
|
||||
params.size_start = size_start_;
|
||||
params.size_end = size_end_;
|
||||
params.fade_in = fade_in_;
|
||||
params.fade_out = fade_out_;
|
||||
params.particle_mode = static_cast<u32>(mode_);
|
||||
params.stretch_factor = stretch_factor_;
|
||||
|
||||
// TODO: 将 params 写入 uniform buffer
|
||||
// uniform_buffer_->write(¶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<particle_instance> particles_;
|
||||
};
|
||||
|
||||
} // namespace mirai
|
||||
313
src/widget/effects/rounded_rect_mask_widget.hpp
Normal file
313
src/widget/effects/rounded_rect_mask_widget.hpp
Normal file
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* @file rounded_rect_mask_widget.hpp
|
||||
* @brief 圆角矩形遮罩控件
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 本文件定义了 rounded_rect_mask_widget 类,实现 Mask 模式的圆角矩形遮罩效果。
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* auto mask = std::make_shared<rounded_rect_mask_widget>();
|
||||
* mask->set_corner_radius(10.0f); // 统一圆角
|
||||
* // 或者设置四角独立圆角
|
||||
* mask->set_corner_radius(10.0f, 20.0f, 10.0f, 20.0f);
|
||||
* mask->set_feather(2.0f); // 边缘羽化
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shader_widget.hpp"
|
||||
#include "types/corner_radius.h"
|
||||
|
||||
#include <span>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// ================================================================================================
|
||||
// RoundedRectMask 绑定类型(模拟代码生成器输出)
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief rounded_rect_mask_widget 的着色器绑定类型
|
||||
*
|
||||
* 此类型模拟代码生成器的输出,提供:
|
||||
* - SPIR-V 字节码访问
|
||||
* - 绑定点常量
|
||||
* - 静态反射信息
|
||||
*
|
||||
* @note 在实际项目中,此类型由 shader_codegen 工具自动生成
|
||||
*/
|
||||
struct rounded_rect_mask_bindings {
|
||||
// ============================================================================================
|
||||
// 着色器存在性标记
|
||||
// ============================================================================================
|
||||
|
||||
static constexpr bool HAS_VERTEX_SHADER = true;
|
||||
static constexpr bool HAS_FRAGMENT_SHADER = true;
|
||||
|
||||
// ============================================================================================
|
||||
// Mask 模式特有标记
|
||||
// ============================================================================================
|
||||
|
||||
static constexpr bool HAS_MASK_SAMPLER = true;
|
||||
static constexpr u32 MASK_BINDING = 0;
|
||||
static constexpr u32 MASK_SET = 0;
|
||||
|
||||
// ============================================================================================
|
||||
// SPIR-V 数据访问(占位实现)
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取顶点着色器 SPIR-V 数据
|
||||
* @return SPIR-V 字节码的 span
|
||||
* @note 实际实现中,此数据由编译后的着色器提供
|
||||
*/
|
||||
[[nodiscard]] static std::span<const u32> get_vertex_spirv() {
|
||||
// 占位:实际数据由着色器编译生成
|
||||
static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number
|
||||
return std::span<const u32>(placeholder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取片段着色器 SPIR-V 数据
|
||||
* @return SPIR-V 字节码的 span
|
||||
* @note 实际实现中,此数据由编译后的着色器提供
|
||||
*/
|
||||
[[nodiscard]] static std::span<const u32> get_fragment_spirv() {
|
||||
// 占位:实际数据由着色器编译生成
|
||||
static const u32 placeholder[] = { 0x07230203 }; // SPIR-V magic number
|
||||
return std::span<const u32>(placeholder);
|
||||
}
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// rounded_rect_mask_widget 类
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 圆角矩形遮罩控件
|
||||
*
|
||||
* 继承自 mask_widget,使用 SDF 生成圆角矩形遮罩。
|
||||
*
|
||||
* 特点:
|
||||
* - 支持四角独立圆角半径
|
||||
* - 可调节的边缘羽化
|
||||
* - 支持边框模式
|
||||
* - 高质量抗锯齿边缘
|
||||
*
|
||||
* @see mask_widget
|
||||
* @see rounded_rect_mask_bindings
|
||||
*/
|
||||
class rounded_rect_mask_widget : public mask_widget<rounded_rect_mask_bindings> {
|
||||
public:
|
||||
MIRAI_OBJECT_TYPE_INFO(rounded_rect_mask_widget, mask_widget<rounded_rect_mask_bindings>)
|
||||
|
||||
// ============================================================================================
|
||||
// 构造与析构
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*
|
||||
* 初始化默认参数:
|
||||
* - corner_radius = 0 (无圆角)
|
||||
* - feather = 0 (无羽化)
|
||||
* - border_width = 0 (填充模式)
|
||||
*/
|
||||
rounded_rect_mask_widget()
|
||||
: corner_radius_{0.0f, 0.0f, 0.0f, 0.0f}
|
||||
, feather_(0.0f)
|
||||
, border_width_(0.0f) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 带统一圆角的构造函数
|
||||
* @param radius 统一圆角半径
|
||||
*/
|
||||
explicit rounded_rect_mask_widget(f32 radius)
|
||||
: corner_radius_{radius, radius, radius, radius}
|
||||
, feather_(0.0f)
|
||||
, border_width_(0.0f) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 带四角独立圆角的构造函数
|
||||
* @param top_left 左上角圆角半径
|
||||
* @param top_right 右上角圆角半径
|
||||
* @param bottom_right 右下角圆角半径
|
||||
* @param bottom_left 左下角圆角半径
|
||||
*/
|
||||
rounded_rect_mask_widget(f32 top_left, f32 top_right, f32 bottom_right, f32 bottom_left)
|
||||
: corner_radius_{top_left, top_right, bottom_right, bottom_left}
|
||||
, feather_(0.0f)
|
||||
, border_width_(0.0f) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 虚析构函数
|
||||
*/
|
||||
~rounded_rect_mask_widget() override = default;
|
||||
|
||||
// ============================================================================================
|
||||
// 圆角设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置统一圆角半径
|
||||
* @param radius 圆角半径(相对于控件尺寸,范围 0-0.5)
|
||||
*/
|
||||
void set_corner_radius(f32 radius) {
|
||||
radius = std::clamp(radius, 0.0f, 0.5f);
|
||||
corner_radius_[0] = radius;
|
||||
corner_radius_[1] = radius;
|
||||
corner_radius_[2] = radius;
|
||||
corner_radius_[3] = radius;
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置四角独立圆角半径
|
||||
* @param top_left 左上角圆角半径
|
||||
* @param top_right 右上角圆角半径
|
||||
* @param bottom_right 右下角圆角半径
|
||||
* @param bottom_left 左下角圆角半径
|
||||
*/
|
||||
void set_corner_radius(f32 top_left, f32 top_right, f32 bottom_right, f32 bottom_left) {
|
||||
corner_radius_[0] = std::clamp(top_left, 0.0f, 0.5f);
|
||||
corner_radius_[1] = std::clamp(top_right, 0.0f, 0.5f);
|
||||
corner_radius_[2] = std::clamp(bottom_right, 0.0f, 0.5f);
|
||||
corner_radius_[3] = std::clamp(bottom_left, 0.0f, 0.5f);
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使用 corner_radius 类型设置圆角
|
||||
* @param radius corner_radius 对象
|
||||
*/
|
||||
void set_corner_radius(const corner_radius& radius) {
|
||||
corner_radius_[0] = std::clamp(radius.top_left, 0.0f, 0.5f);
|
||||
corner_radius_[1] = std::clamp(radius.top_right, 0.0f, 0.5f);
|
||||
corner_radius_[2] = std::clamp(radius.bottom_right, 0.0f, 0.5f);
|
||||
corner_radius_[3] = std::clamp(radius.bottom_left, 0.0f, 0.5f);
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取左上角圆角半径
|
||||
* @return 左上角圆角半径
|
||||
*/
|
||||
[[nodiscard]] f32 top_left_radius() const noexcept { return corner_radius_[0]; }
|
||||
|
||||
/**
|
||||
* @brief 获取右上角圆角半径
|
||||
* @return 右上角圆角半径
|
||||
*/
|
||||
[[nodiscard]] f32 top_right_radius() const noexcept { return corner_radius_[1]; }
|
||||
|
||||
/**
|
||||
* @brief 获取右下角圆角半径
|
||||
* @return 右下角圆角半径
|
||||
*/
|
||||
[[nodiscard]] f32 bottom_right_radius() const noexcept { return corner_radius_[2]; }
|
||||
|
||||
/**
|
||||
* @brief 获取左下角圆角半径
|
||||
* @return 左下角圆角半径
|
||||
*/
|
||||
[[nodiscard]] f32 bottom_left_radius() const noexcept { return corner_radius_[3]; }
|
||||
|
||||
// ============================================================================================
|
||||
// 羽化设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置边缘羽化量
|
||||
* @param feather 羽化量(相对于控件尺寸,范围 0-0.5)
|
||||
*/
|
||||
void set_feather(f32 feather) {
|
||||
feather_ = std::clamp(feather, 0.0f, 0.5f);
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取边缘羽化量
|
||||
* @return 当前羽化量
|
||||
*/
|
||||
[[nodiscard]] f32 feather() const noexcept { return feather_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 边框设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置边框宽度
|
||||
* @param width 边框宽度(0 = 填充模式)
|
||||
*/
|
||||
void set_border_width(f32 width) {
|
||||
border_width_ = std::max(0.0f, width);
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取边框宽度
|
||||
* @return 当前边框宽度
|
||||
*/
|
||||
[[nodiscard]] f32 border_width() const noexcept { return border_width_; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否为边框模式
|
||||
* @return 如果边框宽度大于 0 返回 true
|
||||
*/
|
||||
[[nodiscard]] bool is_border_mode() const noexcept { return border_width_ > 0.0f; }
|
||||
|
||||
protected:
|
||||
// ============================================================================================
|
||||
// 重写基类方法
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 更新 Uniform Buffer
|
||||
*
|
||||
* 将遮罩参数写入 GPU 缓冲区。
|
||||
*/
|
||||
void update_uniform_buffer() override {
|
||||
// RoundedRectMaskParams 结构体布局(与 rounded_rect_mask.slang 中定义一致)
|
||||
struct RoundedRectMaskParams {
|
||||
f32 corner_radius[4];
|
||||
f32 feather;
|
||||
f32 border_width;
|
||||
f32 _padding[2];
|
||||
};
|
||||
|
||||
RoundedRectMaskParams params{};
|
||||
params.corner_radius[0] = corner_radius_[0];
|
||||
params.corner_radius[1] = corner_radius_[1];
|
||||
params.corner_radius[2] = corner_radius_[2];
|
||||
params.corner_radius[3] = corner_radius_[3];
|
||||
params.feather = feather_;
|
||||
params.border_width = border_width_;
|
||||
|
||||
// TODO: 将 params 写入 uniform buffer
|
||||
// uniform_buffer_->write(¶ms, sizeof(params));
|
||||
}
|
||||
|
||||
private:
|
||||
// ============================================================================================
|
||||
// 成员变量
|
||||
// ============================================================================================
|
||||
|
||||
/// 四角圆角半径 (top_left, top_right, bottom_right, bottom_left)
|
||||
f32 corner_radius_[4];
|
||||
|
||||
/// 边缘羽化量
|
||||
f32 feather_;
|
||||
|
||||
/// 边框宽度(0 = 填充模式)
|
||||
f32 border_width_;
|
||||
};
|
||||
|
||||
} // namespace mirai
|
||||
421
src/widget/shader_widget.hpp
Normal file
421
src/widget/shader_widget.hpp
Normal file
@@ -0,0 +1,421 @@
|
||||
/**
|
||||
* @file shader_widget.hpp
|
||||
* @brief MIRAI 框架 shader_widget 模板化基类
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 本文件定义了 shader_widget 模板化基类,包括:
|
||||
* - 类型安全的着色器资源管理
|
||||
* - 编译时绑定类型检查
|
||||
* - 静态反射信息访问
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shader_widget_base.hpp"
|
||||
#include "widget_types.hpp"
|
||||
#include "shader_types.hpp"
|
||||
#include "pipeline.hpp"
|
||||
#include "buffer.hpp"
|
||||
#include "descriptor_pool.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// ================================================================================================
|
||||
// shader_widget 模板类
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 模板化的 shader_widget 基类
|
||||
*
|
||||
* @tparam Bindings 着色器绑定类型,必须满足 shader_bindings concept
|
||||
*
|
||||
* 这是所有着色器控件的基类模板。子类通过继承此类并指定绑定类型,
|
||||
* 获得类型安全的着色器资源管理能力。
|
||||
*
|
||||
* Bindings 类型必须提供:
|
||||
* - `HAS_VERTEX_SHADER` 静态常量
|
||||
* - `HAS_FRAGMENT_SHADER` 静态常量
|
||||
* - `get_vertex_spirv()` 静态方法
|
||||
* - `get_fragment_spirv()` 静态方法
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* // 假设 blur_shader_bindings 是由代码生成器生成的绑定类
|
||||
* class blur_widget : public shader_widget<blur_shader_bindings> {
|
||||
* public:
|
||||
* void set_radius(float radius) {
|
||||
* params_.radius = radius;
|
||||
* mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
* }
|
||||
* protected:
|
||||
* void update_uniform_buffer() override {
|
||||
* // 更新 uniform buffer 数据
|
||||
* }
|
||||
* };
|
||||
* @endcode
|
||||
*/
|
||||
template<typename Bindings>
|
||||
requires shader_bindings<Bindings>
|
||||
class shader_widget : public shader_widget_base {
|
||||
public:
|
||||
MIRAI_OBJECT_TYPE_INFO(shader_widget, shader_widget_base)
|
||||
|
||||
/// 绑定类型别名
|
||||
using bindings_type = Bindings;
|
||||
|
||||
// 编译时验证
|
||||
static_assert(Bindings::HAS_VERTEX_SHADER, "Bindings must have vertex shader");
|
||||
static_assert(Bindings::HAS_FRAGMENT_SHADER, "Bindings must have fragment shader");
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
shader_widget() = default;
|
||||
|
||||
/**
|
||||
* @brief 虚析构函数
|
||||
*/
|
||||
~shader_widget() override = default;
|
||||
|
||||
// ============================================================================================
|
||||
// 静态访问器
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取顶点着色器 SPIR-V 数据
|
||||
* @return SPIR-V 字节码的 span
|
||||
*/
|
||||
[[nodiscard]] static std::span<const u32> vertex_spirv() {
|
||||
return Bindings::get_vertex_spirv();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取片段着色器 SPIR-V 数据
|
||||
* @return SPIR-V 字节码的 span
|
||||
*/
|
||||
[[nodiscard]] static std::span<const u32> fragment_spirv() {
|
||||
return Bindings::get_fragment_spirv();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取静态反射信息
|
||||
*
|
||||
* 如果 Bindings 类型提供了 get_reflection() 方法,则返回反射信息;
|
||||
* 否则返回空的反射信息。
|
||||
*
|
||||
* @return 静态着色器反射信息
|
||||
*/
|
||||
[[nodiscard]] static const static_shader_reflection& reflection() {
|
||||
if constexpr (requires { Bindings::get_reflection(); }) {
|
||||
return Bindings::get_reflection();
|
||||
} else {
|
||||
static const static_shader_reflection empty_reflection{};
|
||||
return empty_reflection;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// ============================================================================================
|
||||
// 资源访问
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取管线句柄
|
||||
* @return Vulkan 管线句柄
|
||||
*/
|
||||
[[nodiscard]] VkPipeline pipeline() const noexcept { return pipeline_; }
|
||||
|
||||
/**
|
||||
* @brief 获取管线布局句柄
|
||||
* @return Vulkan 管线布局句柄
|
||||
*/
|
||||
[[nodiscard]] VkPipelineLayout pipeline_layout() const noexcept { return pipeline_layout_; }
|
||||
|
||||
/**
|
||||
* @brief 获取描述符集
|
||||
* @param index 描述符集索引
|
||||
* @return Vulkan 描述符集句柄,如果索引超出范围返回 VK_NULL_HANDLE
|
||||
*/
|
||||
[[nodiscard]] VkDescriptorSet descriptor_set(u32 index = 0) const noexcept {
|
||||
return index < descriptor_sets_.size() ? descriptor_sets_[index] : VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取描述符集数量
|
||||
* @return 描述符集数量
|
||||
*/
|
||||
[[nodiscard]] size_type descriptor_set_count() const noexcept {
|
||||
return descriptor_sets_.size();
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// 默认实现
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 创建管线(默认实现)
|
||||
*
|
||||
* 子类可以重写此方法以自定义管线创建。
|
||||
* 默认实现使用 vertex_spirv() 和 fragment_spirv() 创建标准的图形管线。
|
||||
*/
|
||||
void create_pipeline() override {
|
||||
// 获取 SPIR-V 数据
|
||||
[[maybe_unused]] auto vert_spirv = vertex_spirv();
|
||||
[[maybe_unused]] auto frag_spirv = fragment_spirv();
|
||||
|
||||
// TODO: 使用 pipeline 类创建管线
|
||||
// 这里需要根据实际的 pipeline 类接口实现
|
||||
//
|
||||
// 示例伪代码:
|
||||
// pipeline_create_info info;
|
||||
// info.vertex_spirv = vert_spirv;
|
||||
// info.fragment_spirv = frag_spirv;
|
||||
// info.descriptor_set_layouts = ...;
|
||||
// pipeline_ = create_graphics_pipeline(device(), info);
|
||||
// pipeline_layout_ = ...;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建描述符集(默认实现)
|
||||
*
|
||||
* 子类可以重写此方法以自定义描述符集创建。
|
||||
* 默认实现根据反射信息创建描述符集。
|
||||
*/
|
||||
void create_descriptor_sets() override {
|
||||
// TODO: 使用 descriptor_pool 创建描述符集
|
||||
// 这里需要根据实际的 descriptor_pool 类接口实现
|
||||
//
|
||||
// 示例伪代码:
|
||||
// auto& refl = reflection();
|
||||
// for (const auto& block : refl.uniform_blocks) {
|
||||
// // 创建描述符集布局
|
||||
// // 分配描述符集
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新 Uniform Buffer(默认实现)
|
||||
*
|
||||
* 子类必须实现此方法来更新自己的 uniform 数据。
|
||||
* 默认为空实现。
|
||||
*/
|
||||
void update_uniform_buffer() override {
|
||||
// 子类必须实现此方法
|
||||
// 默认为空实现
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 记录绘制命令(默认实现)
|
||||
*
|
||||
* 子类可以重写此方法以自定义绘制逻辑。
|
||||
* 默认实现绑定管线和描述符集,绘制全屏四边形。
|
||||
*
|
||||
* @param cmd 命令缓冲区
|
||||
*/
|
||||
void draw(command_buffer& cmd) override {
|
||||
// TODO: 实现默认绘制逻辑
|
||||
//
|
||||
// 示例伪代码:
|
||||
// cmd.bind_pipeline(vk::PipelineBindPoint::eGraphics, pipeline_);
|
||||
// cmd.bind_descriptor_sets(pipeline_layout_, 0, descriptor_sets_);
|
||||
// cmd.draw(6, 1, 0, 0); // 全屏四边形(两个三角形)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 销毁资源(重写)
|
||||
*
|
||||
* 清理模板类特有的资源。
|
||||
*/
|
||||
void destroy() override {
|
||||
// 清理 Vulkan 资源
|
||||
// 注意:实际的资源销毁需要 VkDevice
|
||||
// 这里只是重置句柄
|
||||
|
||||
if (pipeline_ != VK_NULL_HANDLE) {
|
||||
// TODO: vkDestroyPipeline(device().get_handle(), pipeline_, nullptr);
|
||||
pipeline_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (pipeline_layout_ != VK_NULL_HANDLE) {
|
||||
// TODO: vkDestroyPipelineLayout(device().get_handle(), pipeline_layout_, nullptr);
|
||||
pipeline_layout_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// 描述符集由描述符池管理,通常不需要手动释放
|
||||
descriptor_sets_.clear();
|
||||
|
||||
// 调用基类销毁
|
||||
shader_widget_base::destroy();
|
||||
}
|
||||
|
||||
protected:
|
||||
// ============================================================================================
|
||||
// 成员变量
|
||||
// ============================================================================================
|
||||
|
||||
/// Vulkan 管线句柄
|
||||
VkPipeline pipeline_ = VK_NULL_HANDLE;
|
||||
|
||||
/// Vulkan 管线布局句柄
|
||||
VkPipelineLayout pipeline_layout_ = VK_NULL_HANDLE;
|
||||
|
||||
/// 描述符集列表
|
||||
std::vector<VkDescriptorSet> descriptor_sets_;
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// 特化模式模板类(可选扩展)
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief Backdrop 模式控件基类模板
|
||||
*
|
||||
* 用于后效处理模式的控件。提供背景纹理采样能力。
|
||||
*
|
||||
* @tparam Bindings 满足 backdrop_bindings concept 的绑定类型
|
||||
*/
|
||||
template<typename Bindings>
|
||||
requires backdrop_bindings<Bindings>
|
||||
class backdrop_widget : public shader_widget<Bindings> {
|
||||
public:
|
||||
MIRAI_OBJECT_TYPE_INFO(backdrop_widget, shader_widget<Bindings>)
|
||||
|
||||
/**
|
||||
* @brief 获取渲染模式
|
||||
* @return 始终返回 backdrop
|
||||
*/
|
||||
[[nodiscard]] static constexpr shader_widget_mode mode() noexcept {
|
||||
return shader_widget_mode::backdrop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取背景采样器绑定点
|
||||
* @return 绑定点索引
|
||||
*/
|
||||
[[nodiscard]] static constexpr u32 backdrop_binding() noexcept {
|
||||
return Bindings::BACKDROP_BINDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取背景采样器所在描述符集
|
||||
* @return 描述符集索引
|
||||
*/
|
||||
[[nodiscard]] static constexpr u32 backdrop_set() noexcept {
|
||||
return Bindings::BACKDROP_SET;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Procedural 模式控件基类模板
|
||||
*
|
||||
* 用于程序化渲染模式的控件。提供时间和坐标输入。
|
||||
*
|
||||
* @tparam Bindings 满足 procedural_bindings concept 的绑定类型
|
||||
*/
|
||||
template<typename Bindings>
|
||||
requires procedural_bindings<Bindings>
|
||||
class procedural_widget : public shader_widget<Bindings> {
|
||||
public:
|
||||
MIRAI_OBJECT_TYPE_INFO(procedural_widget, shader_widget<Bindings>)
|
||||
|
||||
/**
|
||||
* @brief 获取渲染模式
|
||||
* @return 始终返回 procedural
|
||||
*/
|
||||
[[nodiscard]] static constexpr shader_widget_mode mode() noexcept {
|
||||
return shader_widget_mode::procedural;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取帧数据绑定点
|
||||
* @return 绑定点索引
|
||||
*/
|
||||
[[nodiscard]] static constexpr u32 frame_data_binding() noexcept {
|
||||
return Bindings::FRAME_DATA_BINDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取帧数据所在描述符集
|
||||
* @return 描述符集索引
|
||||
*/
|
||||
[[nodiscard]] static constexpr u32 frame_data_set() noexcept {
|
||||
return Bindings::FRAME_DATA_SET;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Mask 模式控件基类模板
|
||||
*
|
||||
* 用于遮罩效果模式的控件。提供遮罩纹理采样能力。
|
||||
*
|
||||
* @tparam Bindings 满足 mask_bindings concept 的绑定类型
|
||||
*/
|
||||
template<typename Bindings>
|
||||
requires mask_bindings<Bindings>
|
||||
class mask_widget : public shader_widget<Bindings> {
|
||||
public:
|
||||
MIRAI_OBJECT_TYPE_INFO(mask_widget, shader_widget<Bindings>)
|
||||
|
||||
/**
|
||||
* @brief 获取渲染模式
|
||||
* @return 始终返回 mask
|
||||
*/
|
||||
[[nodiscard]] static constexpr shader_widget_mode mode() noexcept {
|
||||
return shader_widget_mode::mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取遮罩纹理绑定点
|
||||
* @return 绑定点索引
|
||||
*/
|
||||
[[nodiscard]] static constexpr u32 mask_binding() noexcept {
|
||||
return Bindings::MASK_BINDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取遮罩纹理所在描述符集
|
||||
* @return 描述符集索引
|
||||
*/
|
||||
[[nodiscard]] static constexpr u32 mask_set() noexcept {
|
||||
return Bindings::MASK_SET;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Mesh 模式控件基类模板
|
||||
*
|
||||
* 用于自定义网格渲染模式的控件。提供自定义顶点数据支持。
|
||||
*
|
||||
* @tparam Bindings 满足 mesh_bindings concept 的绑定类型
|
||||
*/
|
||||
template<typename Bindings>
|
||||
requires mesh_bindings<Bindings>
|
||||
class mesh_widget : public shader_widget<Bindings> {
|
||||
public:
|
||||
MIRAI_OBJECT_TYPE_INFO(mesh_widget, shader_widget<Bindings>)
|
||||
|
||||
/// 顶点类型别名
|
||||
using vertex_type = typename Bindings::VertexType;
|
||||
|
||||
/**
|
||||
* @brief 获取渲染模式
|
||||
* @return 始终返回 mesh
|
||||
*/
|
||||
[[nodiscard]] static constexpr shader_widget_mode mode() noexcept {
|
||||
return shader_widget_mode::mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否有自定义顶点输入
|
||||
* @return 始终返回 true(由 concept 保证)
|
||||
*/
|
||||
[[nodiscard]] static constexpr bool has_custom_vertex_input() noexcept {
|
||||
return Bindings::HAS_CUSTOM_VERTEX_INPUT;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mirai
|
||||
139
src/widget/shader_widget_base.cpp
Normal file
139
src/widget/shader_widget_base.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* @file shader_widget_base.cpp
|
||||
* @brief MIRAI 框架 shader_widget_base 类实现
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*/
|
||||
|
||||
#include "shader_widget_base.hpp"
|
||||
#include "vulkan_device.hpp"
|
||||
#include "allocator.hpp"
|
||||
#include "buffer.hpp"
|
||||
#include "descriptor_pool.hpp"
|
||||
#include "assert.hpp"
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// ================================================================================================
|
||||
// 构造函数和析构函数
|
||||
// ================================================================================================
|
||||
|
||||
shader_widget_base::shader_widget_base() = default;
|
||||
|
||||
shader_widget_base::~shader_widget_base() {
|
||||
if (initialized_) {
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
// 生命周期方法
|
||||
// ================================================================================================
|
||||
|
||||
void shader_widget_base::initialize(vulkan_device& device, gpu_allocator& allocator, descriptor_pool& pool) {
|
||||
MIRAI_ASSERT(!initialized_, "shader_widget_base already initialized");
|
||||
|
||||
device_ = &device;
|
||||
allocator_ = &allocator;
|
||||
pool_ = &pool;
|
||||
|
||||
// 子类负责创建管线和描述符集
|
||||
create_pipeline();
|
||||
create_descriptor_sets();
|
||||
|
||||
initialized_ = true;
|
||||
dirty_flags_ = widget_dirty_flags::all;
|
||||
}
|
||||
|
||||
void shader_widget_base::destroy() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 子类应该重写此方法以清理自己的资源
|
||||
// 基类只负责清理引用
|
||||
device_ = nullptr;
|
||||
allocator_ = nullptr;
|
||||
pool_ = nullptr;
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
// 渲染方法
|
||||
// ================================================================================================
|
||||
|
||||
void shader_widget_base::update(command_buffer& cmd) {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果 uniform_buffer 标记为脏,则更新
|
||||
if (has_flag(dirty_flags_, widget_dirty_flags::uniform_buffer)) {
|
||||
update_uniform_buffer();
|
||||
clear_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
// 状态管理
|
||||
// ================================================================================================
|
||||
|
||||
void shader_widget_base::mark_dirty(widget_dirty_flags flags) {
|
||||
dirty_flags_ |= flags;
|
||||
}
|
||||
|
||||
void shader_widget_base::clear_dirty(widget_dirty_flags flags) {
|
||||
dirty_flags_ = dirty_flags_ & (~flags);
|
||||
}
|
||||
|
||||
bool shader_widget_base::is_dirty(widget_dirty_flags flag) const noexcept {
|
||||
return has_flag(dirty_flags_, flag);
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
// 区域设置
|
||||
// ================================================================================================
|
||||
|
||||
void shader_widget_base::set_bounds(f32 x, f32 y, f32 width, f32 height) {
|
||||
// 检查是否有变化
|
||||
if (x_ != x || y_ != y || width_ != width || height_ != height) {
|
||||
x_ = x;
|
||||
y_ = y;
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
|
||||
// 标记 uniform_buffer 为脏,因为边界变化通常需要更新 uniform 数据
|
||||
mark_dirty(widget_dirty_flags::uniform_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
// 辅助方法
|
||||
// ================================================================================================
|
||||
|
||||
std::unique_ptr<buffer> shader_widget_base::create_uniform_buffer(size_type size) {
|
||||
MIRAI_ASSERT(allocator_ != nullptr, "allocator not set");
|
||||
|
||||
// 创建 buffer_create_info
|
||||
buffer_create_info info;
|
||||
info.size = static_cast<u64>(size);
|
||||
info.usage = buffer_usage::uniform;
|
||||
info.mem_usage = memory_usage::cpu_to_gpu;
|
||||
info.persistent_mapped = true;
|
||||
info.debug_name = "shader_widget_uniform";
|
||||
|
||||
// 注意:buffer 类需要 shared_ptr<gpu_allocator>,
|
||||
// 但我们这里只有原始指针。在实际使用中,
|
||||
// 可能需要通过其他方式获取 shared_ptr,
|
||||
// 或者修改 buffer 类接口。
|
||||
//
|
||||
// 这里返回 nullptr 作为占位实现,
|
||||
// 实际的 buffer 创建需要在子类中处理,
|
||||
// 因为子类可能有访问 shared_ptr<gpu_allocator> 的方式。
|
||||
|
||||
// TODO: 实现实际的 buffer 创建
|
||||
// 当前返回空指针,子类需要重写此方法或使用其他方式创建 buffer
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace mirai
|
||||
309
src/widget/shader_widget_base.hpp
Normal file
309
src/widget/shader_widget_base.hpp
Normal file
@@ -0,0 +1,309 @@
|
||||
/**
|
||||
* @file shader_widget_base.hpp
|
||||
* @brief MIRAI 框架 shader_widget 非模板基类
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 本文件定义了 shader_widget 的非模板基类,包括:
|
||||
* - 基本的渲染资源管理能力
|
||||
* - 生命周期管理
|
||||
* - 脏标记系统
|
||||
* - 区域设置
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "object.hpp"
|
||||
#include "widget_types.hpp"
|
||||
#include "vulkan_types.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// ================================================================================================
|
||||
// 前向声明
|
||||
// ================================================================================================
|
||||
|
||||
class vulkan_device;
|
||||
class gpu_allocator;
|
||||
class descriptor_pool;
|
||||
class command_buffer;
|
||||
class buffer;
|
||||
class texture_view;
|
||||
class sampler;
|
||||
|
||||
// ================================================================================================
|
||||
// shader_widget_base 类
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief shader_widget 非模板基类
|
||||
*
|
||||
* 提供所有 shader_widget 的公共实现,避免模板代码膨胀。
|
||||
* 子类通过继承此类获取基本的渲染资源管理能力。
|
||||
*
|
||||
* @note 此类不能直接实例化,必须通过派生类使用
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* // shader_widget_base 作为所有着色器控件的基类
|
||||
* // 通常不直接使用,而是通过模板化的 shader_widget<Bindings> 使用
|
||||
* class my_widget : public shader_widget<my_bindings> {
|
||||
* // 自定义实现
|
||||
* };
|
||||
* @endcode
|
||||
*/
|
||||
class shader_widget_base : public object {
|
||||
public:
|
||||
MIRAI_OBJECT_TYPE_INFO(shader_widget_base, object)
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
shader_widget_base();
|
||||
|
||||
/**
|
||||
* @brief 虚析构函数
|
||||
*/
|
||||
~shader_widget_base() override;
|
||||
|
||||
// 禁止拷贝
|
||||
shader_widget_base(const shader_widget_base&) = delete;
|
||||
shader_widget_base& operator=(const shader_widget_base&) = delete;
|
||||
|
||||
// 禁止移动(因为只能通过智能指针管理)
|
||||
shader_widget_base(shader_widget_base&&) = delete;
|
||||
shader_widget_base& operator=(shader_widget_base&&) = delete;
|
||||
|
||||
// ============================================================================================
|
||||
// 生命周期方法
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 初始化 GPU 资源
|
||||
*
|
||||
* 初始化控件所需的 GPU 资源,包括管线、描述符集等。
|
||||
* 必须在渲染前调用此方法。
|
||||
*
|
||||
* @param device Vulkan 设备
|
||||
* @param allocator GPU 内存分配器
|
||||
* @param pool 描述符池
|
||||
*/
|
||||
virtual void initialize(vulkan_device& device, gpu_allocator& allocator, descriptor_pool& pool);
|
||||
|
||||
/**
|
||||
* @brief 销毁 GPU 资源
|
||||
*
|
||||
* 销毁控件持有的所有 GPU 资源。
|
||||
* 在销毁控件或需要重新初始化时调用。
|
||||
*/
|
||||
virtual void destroy();
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
* @return 如果已初始化返回 true
|
||||
*/
|
||||
[[nodiscard]] bool is_initialized() const noexcept { return initialized_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 渲染方法
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 更新脏资源
|
||||
*
|
||||
* 检查并更新所有标记为脏的资源。
|
||||
* 应在 draw() 之前调用。
|
||||
*
|
||||
* @param cmd 命令缓冲区(用于可能的数据传输)
|
||||
*/
|
||||
virtual void update(command_buffer& cmd);
|
||||
|
||||
/**
|
||||
* @brief 记录绘制命令
|
||||
*
|
||||
* 将绘制命令记录到命令缓冲区。
|
||||
* 子类必须实现此方法。
|
||||
*
|
||||
* @param cmd 命令缓冲区
|
||||
*/
|
||||
virtual void draw(command_buffer& cmd) = 0;
|
||||
|
||||
// ============================================================================================
|
||||
// 状态管理
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 标记为脏
|
||||
*
|
||||
* 标记指定的资源需要更新。
|
||||
*
|
||||
* @param flags 脏标记,默认为所有标记
|
||||
*/
|
||||
void mark_dirty(widget_dirty_flags flags = widget_dirty_flags::all);
|
||||
|
||||
/**
|
||||
* @brief 清除脏标记
|
||||
*
|
||||
* 清除指定的脏标记。
|
||||
*
|
||||
* @param flags 要清除的标记
|
||||
*/
|
||||
void clear_dirty(widget_dirty_flags flags);
|
||||
|
||||
/**
|
||||
* @brief 检查是否脏
|
||||
*
|
||||
* 检查是否有指定的脏标记。
|
||||
*
|
||||
* @param flag 要检查的标记
|
||||
* @return 如果有指定标记返回 true
|
||||
*/
|
||||
[[nodiscard]] bool is_dirty(widget_dirty_flags flag) const noexcept;
|
||||
|
||||
/**
|
||||
* @brief 获取当前脏标记
|
||||
* @return 当前的脏标记集合
|
||||
*/
|
||||
[[nodiscard]] widget_dirty_flags dirty_flags() const noexcept { return dirty_flags_; }
|
||||
|
||||
// ============================================================================================
|
||||
// 区域设置
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 设置控件区域(相对于父控件)
|
||||
*
|
||||
* @param x X 坐标
|
||||
* @param y Y 坐标
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
*/
|
||||
void set_bounds(f32 x, f32 y, f32 width, f32 height);
|
||||
|
||||
/**
|
||||
* @brief 获取 X 坐标
|
||||
* @return X 坐标
|
||||
*/
|
||||
[[nodiscard]] f32 x() const noexcept { return x_; }
|
||||
|
||||
/**
|
||||
* @brief 获取 Y 坐标
|
||||
* @return Y 坐标
|
||||
*/
|
||||
[[nodiscard]] f32 y() const noexcept { return y_; }
|
||||
|
||||
/**
|
||||
* @brief 获取宽度
|
||||
* @return 宽度
|
||||
*/
|
||||
[[nodiscard]] f32 width() const noexcept { return width_; }
|
||||
|
||||
/**
|
||||
* @brief 获取高度
|
||||
* @return 高度
|
||||
*/
|
||||
[[nodiscard]] f32 height() const noexcept { return height_; }
|
||||
|
||||
protected:
|
||||
// ============================================================================================
|
||||
// 子类实现的钩子方法
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 创建管线
|
||||
*
|
||||
* 子类必须实现此方法来创建渲染管线。
|
||||
* 在 initialize() 中调用。
|
||||
*/
|
||||
virtual void create_pipeline() = 0;
|
||||
|
||||
/**
|
||||
* @brief 创建描述符集
|
||||
*
|
||||
* 子类必须实现此方法来创建描述符集。
|
||||
* 在 initialize() 中调用。
|
||||
*/
|
||||
virtual void create_descriptor_sets() = 0;
|
||||
|
||||
/**
|
||||
* @brief 更新 Uniform Buffer
|
||||
*
|
||||
* 子类必须实现此方法来更新 Uniform Buffer 数据。
|
||||
* 在 update() 中当 uniform_buffer 标记为脏时调用。
|
||||
*/
|
||||
virtual void update_uniform_buffer() = 0;
|
||||
|
||||
// ============================================================================================
|
||||
// 辅助方法
|
||||
// ============================================================================================
|
||||
|
||||
/**
|
||||
* @brief 创建 Uniform Buffer
|
||||
*
|
||||
* 使用 GPU 分配器创建指定大小的 Uniform Buffer。
|
||||
*
|
||||
* @param size 缓冲区大小(字节)
|
||||
* @return 创建的缓冲区
|
||||
*/
|
||||
[[nodiscard]] std::unique_ptr<buffer> create_uniform_buffer(size_type size);
|
||||
|
||||
/**
|
||||
* @brief 获取设备引用
|
||||
* @return Vulkan 设备引用
|
||||
*/
|
||||
[[nodiscard]] vulkan_device& device() { return *device_; }
|
||||
|
||||
/**
|
||||
* @brief 获取设备引用(const 版本)
|
||||
* @return Vulkan 设备常量引用
|
||||
*/
|
||||
[[nodiscard]] const vulkan_device& device() const { return *device_; }
|
||||
|
||||
/**
|
||||
* @brief 获取分配器引用
|
||||
* @return GPU 内存分配器引用
|
||||
*/
|
||||
[[nodiscard]] gpu_allocator& allocator() { return *allocator_; }
|
||||
|
||||
/**
|
||||
* @brief 获取描述符池引用
|
||||
* @return 描述符池引用
|
||||
*/
|
||||
[[nodiscard]] descriptor_pool& pool() { return *pool_; }
|
||||
|
||||
protected:
|
||||
// ============================================================================================
|
||||
// 成员变量
|
||||
// ============================================================================================
|
||||
|
||||
/// 设备引用(不持有所有权)
|
||||
vulkan_device* device_ = nullptr;
|
||||
|
||||
/// 分配器引用(不持有所有权)
|
||||
gpu_allocator* allocator_ = nullptr;
|
||||
|
||||
/// 描述符池引用(不持有所有权)
|
||||
descriptor_pool* pool_ = nullptr;
|
||||
|
||||
/// 是否已初始化
|
||||
bool initialized_ = false;
|
||||
|
||||
/// 脏标记
|
||||
widget_dirty_flags dirty_flags_ = widget_dirty_flags::all;
|
||||
|
||||
/// X 坐标
|
||||
f32 x_ = 0.0f;
|
||||
|
||||
/// Y 坐标
|
||||
f32 y_ = 0.0f;
|
||||
|
||||
/// 宽度
|
||||
f32 width_ = 0.0f;
|
||||
|
||||
/// 高度
|
||||
f32 height_ = 0.0f;
|
||||
};
|
||||
|
||||
} // namespace mirai
|
||||
220
src/widget/widget_types.hpp
Normal file
220
src/widget/widget_types.hpp
Normal file
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* @file widget_types.hpp
|
||||
* @brief MIRAI 框架 Widget 模块基础类型定义
|
||||
* @author MIRAI Team
|
||||
* @version 0.1.0
|
||||
*
|
||||
* 本文件定义了 Widget 模块使用的基础类型,包括:
|
||||
* - 着色器绑定 Concepts
|
||||
* - 渲染模式枚举
|
||||
* - 脏标记枚举
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types/types.hpp"
|
||||
|
||||
#include <concepts>
|
||||
#include <span>
|
||||
#include <cstdint>
|
||||
|
||||
namespace mirai {
|
||||
|
||||
// ================================================================================================
|
||||
// 前向声明
|
||||
// ================================================================================================
|
||||
|
||||
class vulkan_device;
|
||||
class gpu_allocator;
|
||||
class descriptor_pool;
|
||||
class command_buffer;
|
||||
class texture_view;
|
||||
class sampler;
|
||||
|
||||
// ================================================================================================
|
||||
// 着色器绑定 Concepts
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 基础绑定检查
|
||||
*
|
||||
* 所有绑定类型必须满足此 concept,提供基本的着色器信息
|
||||
*/
|
||||
template<typename T>
|
||||
concept shader_bindings = requires {
|
||||
/// 是否有顶点着色器
|
||||
{ T::HAS_VERTEX_SHADER } -> std::convertible_to<bool>;
|
||||
/// 是否有片段着色器
|
||||
{ T::HAS_FRAGMENT_SHADER } -> std::convertible_to<bool>;
|
||||
/// 获取顶点着色器 SPIR-V 数据
|
||||
{ T::get_vertex_spirv() } -> std::same_as<std::span<const u32>>;
|
||||
/// 获取片段着色器 SPIR-V 数据
|
||||
{ T::get_fragment_spirv() } -> std::same_as<std::span<const u32>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Backdrop 模式绑定检查
|
||||
*
|
||||
* 用于后效处理模式的绑定类型必须满足此 concept
|
||||
*/
|
||||
template<typename T>
|
||||
concept backdrop_bindings = shader_bindings<T> && requires {
|
||||
/// 是否有背景采样器
|
||||
{ T::HAS_BACKDROP_SAMPLER } -> std::convertible_to<bool>;
|
||||
/// 背景纹理绑定点
|
||||
{ T::BACKDROP_BINDING } -> std::convertible_to<u32>;
|
||||
/// 背景纹理所在描述符集
|
||||
{ T::BACKDROP_SET } -> std::convertible_to<u32>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Procedural 模式绑定检查
|
||||
*
|
||||
* 用于程序化渲染模式的绑定类型必须满足此 concept
|
||||
*/
|
||||
template<typename T>
|
||||
concept procedural_bindings = shader_bindings<T> && requires {
|
||||
/// 是否有帧数据
|
||||
{ T::HAS_FRAME_DATA } -> std::convertible_to<bool>;
|
||||
/// 帧数据绑定点
|
||||
{ T::FRAME_DATA_BINDING } -> std::convertible_to<u32>;
|
||||
/// 帧数据所在描述符集
|
||||
{ T::FRAME_DATA_SET } -> std::convertible_to<u32>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Mask 模式绑定检查
|
||||
*
|
||||
* 用于遮罩效果模式的绑定类型必须满足此 concept
|
||||
*/
|
||||
template<typename T>
|
||||
concept mask_bindings = shader_bindings<T> && requires {
|
||||
/// 是否有遮罩采样器
|
||||
{ T::HAS_MASK_SAMPLER } -> std::convertible_to<bool>;
|
||||
/// 遮罩纹理绑定点
|
||||
{ T::MASK_BINDING } -> std::convertible_to<u32>;
|
||||
/// 遮罩纹理所在描述符集
|
||||
{ T::MASK_SET } -> std::convertible_to<u32>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Mesh 模式绑定检查
|
||||
*
|
||||
* 用于自定义网格渲染模式的绑定类型必须满足此 concept
|
||||
*/
|
||||
template<typename T>
|
||||
concept mesh_bindings = shader_bindings<T> && requires {
|
||||
/// 是否有自定义顶点输入
|
||||
{ T::HAS_CUSTOM_VERTEX_INPUT } -> std::convertible_to<bool>;
|
||||
/// 顶点类型
|
||||
typename T::VertexType;
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// 渲染模式枚举
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 着色器控件渲染模式
|
||||
*
|
||||
* 定义了着色器控件支持的四种渲染模式
|
||||
*/
|
||||
enum class shader_widget_mode : u8 {
|
||||
/// 后效处理模式:采样背景纹理进行处理
|
||||
backdrop,
|
||||
/// 程序化渲染模式:基于时间和坐标的程序化生成
|
||||
procedural,
|
||||
/// 遮罩模式:使用遮罩纹理进行裁剪或混合
|
||||
mask,
|
||||
/// 自定义网格模式:使用自定义顶点数据渲染
|
||||
mesh
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// 脏标记枚举
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 控件脏标记
|
||||
*
|
||||
* 用于标记哪些资源需要更新
|
||||
*/
|
||||
enum class widget_dirty_flags : u32 {
|
||||
/// 无脏标记
|
||||
none = 0,
|
||||
/// Uniform Buffer 需要更新
|
||||
uniform_buffer = 1 << 0,
|
||||
/// 描述符集需要更新
|
||||
descriptor_set = 1 << 1,
|
||||
/// 管线需要重建
|
||||
pipeline = 1 << 2,
|
||||
/// 顶点缓冲区需要更新
|
||||
vertex_buffer = 1 << 3,
|
||||
/// 所有标记
|
||||
all = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// 位运算支持
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* @brief 脏标记按位或运算
|
||||
* @param a 第一个操作数
|
||||
* @param b 第二个操作数
|
||||
* @return 运算结果
|
||||
*/
|
||||
constexpr widget_dirty_flags operator|(widget_dirty_flags a, widget_dirty_flags b) {
|
||||
return static_cast<widget_dirty_flags>(static_cast<u32>(a) | static_cast<u32>(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 脏标记按位与运算
|
||||
* @param a 第一个操作数
|
||||
* @param b 第二个操作数
|
||||
* @return 运算结果
|
||||
*/
|
||||
constexpr widget_dirty_flags operator&(widget_dirty_flags a, widget_dirty_flags b) {
|
||||
return static_cast<widget_dirty_flags>(static_cast<u32>(a) & static_cast<u32>(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 脏标记按位或赋值运算
|
||||
* @param a 被赋值的操作数
|
||||
* @param b 第二个操作数
|
||||
* @return 赋值后的引用
|
||||
*/
|
||||
constexpr widget_dirty_flags& operator|=(widget_dirty_flags& a, widget_dirty_flags b) {
|
||||
return a = a | b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 脏标记按位与赋值运算
|
||||
* @param a 被赋值的操作数
|
||||
* @param b 第二个操作数
|
||||
* @return 赋值后的引用
|
||||
*/
|
||||
constexpr widget_dirty_flags& operator&=(widget_dirty_flags& a, widget_dirty_flags b) {
|
||||
return a = a & b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 脏标记按位取反运算
|
||||
* @param a 操作数
|
||||
* @return 取反结果
|
||||
*/
|
||||
constexpr widget_dirty_flags operator~(widget_dirty_flags a) {
|
||||
return static_cast<widget_dirty_flags>(~static_cast<u32>(a));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否包含指定标记
|
||||
* @param flags 标记集合
|
||||
* @param flag 要检查的标记
|
||||
* @return 如果包含返回 true
|
||||
*/
|
||||
[[nodiscard]] constexpr bool has_flag(widget_dirty_flags flags, widget_dirty_flags flag) {
|
||||
return (static_cast<u32>(flags) & static_cast<u32>(flag)) != 0;
|
||||
}
|
||||
|
||||
} // namespace mirai
|
||||
BIN
tools/__pycache__/generate_shader_bindings.cpython-314.pyc
Normal file
BIN
tools/__pycache__/generate_shader_bindings.cpython-314.pyc
Normal file
Binary file not shown.
745
tools/generate_shader_bindings.py
Normal file
745
tools/generate_shader_bindings.py
Normal file
@@ -0,0 +1,745 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
MIRAI 着色器绑定代码生成器
|
||||
|
||||
读取 SPIR-V 和反射 JSON,生成 C++ 绑定代码。
|
||||
|
||||
Usage:
|
||||
python generate_shader_bindings.py \
|
||||
--dir ./shader_intermediate \
|
||||
--output generated/shader_name_bindings.hpp \
|
||||
--name ShaderName
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import struct
|
||||
import re
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Dict, Optional, Any
|
||||
from datetime import datetime
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 数据结构
|
||||
# ============================================================================
|
||||
|
||||
@dataclass
|
||||
class UniformMember:
|
||||
"""Uniform Buffer 成员"""
|
||||
name: str
|
||||
type: str
|
||||
offset: int
|
||||
size: int
|
||||
array_size: int = 1
|
||||
|
||||
|
||||
@dataclass
|
||||
class UniformBuffer:
|
||||
"""Uniform Buffer 描述"""
|
||||
name: str
|
||||
set: int
|
||||
binding: int
|
||||
size: int
|
||||
members: List[UniformMember] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PushConstant:
|
||||
"""Push Constant 描述"""
|
||||
name: str
|
||||
size: int
|
||||
stage_flags: int = 0 # Vulkan 着色器阶段标志位掩码
|
||||
offset: int = 0 # Push constant 偏移量(通常为 0)
|
||||
members: List[UniformMember] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SamplerBinding:
|
||||
"""采样器绑定描述"""
|
||||
name: str
|
||||
set: int
|
||||
binding: int
|
||||
dimension: str = "2D"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ShaderStage:
|
||||
"""着色器阶段"""
|
||||
stage: str # vertex, fragment, compute
|
||||
entry_point: str
|
||||
spirv: bytes = b''
|
||||
|
||||
|
||||
@dataclass
|
||||
class ShaderReflection:
|
||||
"""着色器反射数据"""
|
||||
name: str
|
||||
stages: List[ShaderStage] = field(default_factory=list)
|
||||
uniform_buffers: List[UniformBuffer] = field(default_factory=list)
|
||||
push_constants: List[PushConstant] = field(default_factory=list)
|
||||
samplers: List[SamplerBinding] = field(default_factory=list)
|
||||
# 每个阶段特有的资源
|
||||
stage_uniform_buffers: Dict[str, List[UniformBuffer]] = field(default_factory=dict)
|
||||
stage_samplers: Dict[str, List[SamplerBinding]] = field(default_factory=dict)
|
||||
stage_push_constants: Dict[str, List[PushConstant]] = field(default_factory=dict)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 类型映射
|
||||
# ============================================================================
|
||||
|
||||
# Slang/HLSL 类型到 C++ 类型的映射
|
||||
SLANG_TO_CPP_TYPE = {
|
||||
"float": "float",
|
||||
"float2": "Eigen::Vector2f",
|
||||
"float3": "Eigen::Vector3f",
|
||||
"float4": "Eigen::Vector4f",
|
||||
"int": "int32_t",
|
||||
"int2": "Eigen::Vector2i",
|
||||
"int3": "Eigen::Vector3i",
|
||||
"int4": "Eigen::Vector4i",
|
||||
"uint": "uint32_t",
|
||||
"uint2": "Eigen::Matrix<uint32_t, 2, 1>",
|
||||
"uint3": "Eigen::Matrix<uint32_t, 3, 1>",
|
||||
"uint4": "Eigen::Matrix<uint32_t, 4, 1>",
|
||||
"float2x2": "Eigen::Matrix2f",
|
||||
"float3x3": "Eigen::Matrix3f",
|
||||
"float4x4": "Eigen::Matrix4f",
|
||||
"bool": "uint32_t", # GLSL bool 是 4 字节
|
||||
"matrix": "Eigen::Matrix4f",
|
||||
}
|
||||
|
||||
# STD140 对齐规则
|
||||
STD140_ALIGNMENT = {
|
||||
"float": 4,
|
||||
"float2": 8,
|
||||
"float3": 16,
|
||||
"float4": 16,
|
||||
"int": 4,
|
||||
"int2": 8,
|
||||
"int3": 16,
|
||||
"int4": 16,
|
||||
"uint": 4,
|
||||
"uint2": 8,
|
||||
"uint3": 16,
|
||||
"uint4": 16,
|
||||
"float2x2": 16,
|
||||
"float3x3": 16,
|
||||
"float4x4": 16,
|
||||
"bool": 4,
|
||||
"matrix": 16,
|
||||
}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 解析器
|
||||
# ============================================================================
|
||||
|
||||
def parse_reflection_json(json_path: Path) -> ShaderReflection:
|
||||
"""解析 Slang 生成的反射 JSON"""
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
name = json_path.stem.replace('.reflect', '')
|
||||
|
||||
# 从文件名检测阶段
|
||||
stage = detect_stage_from_filename(json_path.name)
|
||||
if stage is None:
|
||||
# 尝试从反射数据中获取阶段
|
||||
stage = data.get('stage', None)
|
||||
|
||||
# 解析 Uniform Buffers
|
||||
uniform_buffers = []
|
||||
for ub in data.get('uniformBuffers', []):
|
||||
members = [
|
||||
UniformMember(
|
||||
name=m['name'],
|
||||
type=m['type'],
|
||||
offset=m['offset'],
|
||||
size=m['size'],
|
||||
array_size=m.get('arraySize', 1)
|
||||
)
|
||||
for m in ub.get('members', [])
|
||||
]
|
||||
uniform_buffers.append(UniformBuffer(
|
||||
name=ub['name'],
|
||||
set=ub['set'],
|
||||
binding=ub['binding'],
|
||||
size=ub['size'],
|
||||
members=members
|
||||
))
|
||||
|
||||
# 解析 Push Constants
|
||||
push_constants = []
|
||||
for pc in data.get('pushConstants', []):
|
||||
members = [
|
||||
UniformMember(
|
||||
name=m['name'],
|
||||
type=m['type'],
|
||||
offset=m['offset'],
|
||||
size=m['size']
|
||||
)
|
||||
for m in pc.get('members', [])
|
||||
]
|
||||
push_constants.append(PushConstant(
|
||||
name=pc['name'],
|
||||
size=pc['size'],
|
||||
stage_flags=pc.get('stageFlags', 0),
|
||||
offset=pc.get('offset', 0),
|
||||
members=members
|
||||
))
|
||||
|
||||
# 解析 Samplers
|
||||
samplers = []
|
||||
for s in data.get('samplers', []):
|
||||
samplers.append(SamplerBinding(
|
||||
name=s['name'],
|
||||
set=s['set'],
|
||||
binding=s['binding'],
|
||||
dimension=s.get('dimension', '2D')
|
||||
))
|
||||
|
||||
# 按阶段存储的资源
|
||||
stage_uniform_buffers = {}
|
||||
stage_samplers = {}
|
||||
stage_push_constants = {}
|
||||
|
||||
if stage:
|
||||
stage_key = get_stage_suffix(stage).lower() # 使用小写作为字典键
|
||||
stage_uniform_buffers[stage_key] = uniform_buffers
|
||||
stage_samplers[stage_key] = samplers
|
||||
stage_push_constants[stage_key] = push_constants
|
||||
|
||||
return ShaderReflection(
|
||||
name=name,
|
||||
stages=[],
|
||||
uniform_buffers=uniform_buffers,
|
||||
push_constants=push_constants,
|
||||
samplers=samplers,
|
||||
stage_uniform_buffers=stage_uniform_buffers,
|
||||
stage_samplers=stage_samplers,
|
||||
stage_push_constants=stage_push_constants
|
||||
)
|
||||
|
||||
|
||||
def merge_reflections(reflections: List[ShaderReflection]) -> ShaderReflection:
|
||||
"""合并多个反射数据(取并集)"""
|
||||
if not reflections:
|
||||
return ShaderReflection(name="merged")
|
||||
|
||||
if len(reflections) == 1:
|
||||
return reflections[0]
|
||||
|
||||
# 使用第一个反射的名称作为合并后的名称
|
||||
merged_name = reflections[0].name
|
||||
|
||||
# 合并 uniform buffers(按 set/binding 去重)
|
||||
uniform_buffers_dict: Dict[tuple, UniformBuffer] = {}
|
||||
for ref in reflections:
|
||||
for ub in ref.uniform_buffers:
|
||||
key = (ub.set, ub.binding)
|
||||
if key not in uniform_buffers_dict:
|
||||
uniform_buffers_dict[key] = ub
|
||||
|
||||
# 合并 push constants(按名称去重)
|
||||
push_constants_dict: Dict[str, PushConstant] = {}
|
||||
for ref in reflections:
|
||||
for pc in ref.push_constants:
|
||||
if pc.name not in push_constants_dict:
|
||||
push_constants_dict[pc.name] = pc
|
||||
|
||||
# 合并 samplers(按 set/binding 去重)
|
||||
samplers_dict: Dict[tuple, SamplerBinding] = {}
|
||||
for ref in reflections:
|
||||
for s in ref.samplers:
|
||||
key = (s.set, s.binding)
|
||||
if key not in samplers_dict:
|
||||
samplers_dict[key] = s
|
||||
|
||||
# 合并每个阶段特有的资源
|
||||
stage_uniform_buffers: Dict[str, List[UniformBuffer]] = {}
|
||||
stage_samplers: Dict[str, List[SamplerBinding]] = {}
|
||||
stage_push_constants: Dict[str, List[PushConstant]] = {}
|
||||
|
||||
for ref in reflections:
|
||||
for stage_key, ubs in ref.stage_uniform_buffers.items():
|
||||
if stage_key not in stage_uniform_buffers:
|
||||
stage_uniform_buffers[stage_key] = []
|
||||
stage_uniform_buffers[stage_key].extend(ubs)
|
||||
|
||||
for stage_key, samps in ref.stage_samplers.items():
|
||||
if stage_key not in stage_samplers:
|
||||
stage_samplers[stage_key] = []
|
||||
stage_samplers[stage_key].extend(samps)
|
||||
|
||||
for stage_key, pcs in ref.stage_push_constants.items():
|
||||
if stage_key not in stage_push_constants:
|
||||
stage_push_constants[stage_key] = []
|
||||
stage_push_constants[stage_key].extend(pcs)
|
||||
|
||||
return ShaderReflection(
|
||||
name=merged_name,
|
||||
stages=[],
|
||||
uniform_buffers=list(uniform_buffers_dict.values()),
|
||||
push_constants=list(push_constants_dict.values()),
|
||||
samplers=list(samplers_dict.values()),
|
||||
stage_uniform_buffers=stage_uniform_buffers,
|
||||
stage_samplers=stage_samplers,
|
||||
stage_push_constants=stage_push_constants
|
||||
)
|
||||
|
||||
|
||||
def load_spirv(spirv_path: Path) -> bytes:
|
||||
"""加载 SPIR-V 二进制文件"""
|
||||
with open(spirv_path, 'rb') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def detect_stage_from_filename(filename: str) -> Optional[str]:
|
||||
"""从文件名检测着色器阶段"""
|
||||
filename_lower = filename.lower()
|
||||
if '.vert.' in filename_lower or filename_lower.endswith('.vert'):
|
||||
return 'vertex'
|
||||
elif '.frag.' in filename_lower or filename_lower.endswith('.frag'):
|
||||
return 'fragment'
|
||||
elif '.comp.' in filename_lower or filename_lower.endswith('.comp'):
|
||||
return 'compute'
|
||||
elif '.geom.' in filename_lower or filename_lower.endswith('.geom'):
|
||||
return 'geometry'
|
||||
elif '.tesc.' in filename_lower or filename_lower.endswith('.tesc'):
|
||||
return 'tessellation_control'
|
||||
elif '.tese.' in filename_lower or filename_lower.endswith('.tese'):
|
||||
return 'tessellation_evaluation'
|
||||
return None
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 辅助函数
|
||||
# ============================================================================
|
||||
|
||||
def to_snake_case(name: str) -> str:
|
||||
"""转换为 snake_case"""
|
||||
# 如果已经是全小写下划线连接,不做转换
|
||||
if name.islower() and '_' in name:
|
||||
return name
|
||||
|
||||
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
||||
snake = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
||||
# 处理连续大写字母的情况(如 "ABCDef" -> "abc_def")
|
||||
snake = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1_\2', snake)
|
||||
snake = re.sub(r'([a-z])([A-Z])', r'\1_\2', snake).lower()
|
||||
return snake
|
||||
|
||||
|
||||
def to_camel_case(name: str) -> str:
|
||||
"""转换为 CamelCase(首字母大写的驼峰式)"""
|
||||
parts = name.split('_')
|
||||
return ''.join(part.title() for part in parts if part)
|
||||
|
||||
|
||||
def to_upper_snake_case(name: str) -> str:
|
||||
"""转换为 UPPER_SNAKE_CASE"""
|
||||
return to_snake_case(name).upper()
|
||||
|
||||
|
||||
def get_stage_suffix(stage: str) -> str:
|
||||
"""获取着色器阶段后缀"""
|
||||
return {
|
||||
'vertex': 'VERT',
|
||||
'fragment': 'FRAG',
|
||||
'compute': 'COMP',
|
||||
'geometry': 'GEOM',
|
||||
'tessellation_control': 'TESC',
|
||||
'tessellation_evaluation': 'TESE',
|
||||
}.get(stage, stage.upper())
|
||||
|
||||
|
||||
def spirv_to_hex_lines(spirv: bytes) -> List[str]:
|
||||
"""将 SPIR-V 转换为十六进制行"""
|
||||
if len(spirv) == 0:
|
||||
return []
|
||||
|
||||
word_count = len(spirv) // 4
|
||||
words = struct.unpack(f'<{word_count}I', spirv[:word_count * 4])
|
||||
|
||||
lines = []
|
||||
for i in range(0, len(words), 8):
|
||||
chunk = words[i:i+8]
|
||||
hex_values = ', '.join(f'0x{w:08x}' for w in chunk)
|
||||
lines.append(f"{hex_values},")
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def prepare_template_context(
|
||||
shader_name: str,
|
||||
reflection: ShaderReflection,
|
||||
stages: Dict[str, bytes]
|
||||
) -> Dict[str, Any]:
|
||||
"""准备 Jinja2 模板上下文"""
|
||||
upper_snake_name = to_upper_snake_case(shader_name)
|
||||
guard = f"MIRAI_GENERATED_SHADER_{upper_snake_name}_HPP"
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# 准备 SPIR-V 数据
|
||||
stages_data = {}
|
||||
for stage, spirv in stages.items():
|
||||
stage_suffix = get_stage_suffix(stage)
|
||||
word_count = len(spirv) // 4 if spirv else 0
|
||||
stages_data[stage_suffix] = {
|
||||
'word_count': word_count,
|
||||
'hex_lines': spirv_to_hex_lines(spirv),
|
||||
}
|
||||
|
||||
# 准备 Uniform Buffer 数据
|
||||
uniform_buffers_data = []
|
||||
for ub in reflection.uniform_buffers:
|
||||
struct_name = ub.name # 保持原始名称
|
||||
members_data = []
|
||||
calculated_size = 0
|
||||
for member in ub.members:
|
||||
cpp_type = SLANG_TO_CPP_TYPE.get(member.type, member.type)
|
||||
alignment = STD140_ALIGNMENT.get(member.type, 16)
|
||||
if member.array_size > 1:
|
||||
cpp_type = f"std::array<{cpp_type}, {member.array_size}>"
|
||||
members_data.append({
|
||||
'name': member.name, # 保持原始名称
|
||||
'type_name': member.type,
|
||||
'cpp_type': cpp_type,
|
||||
'alignment': alignment,
|
||||
'offset': member.offset,
|
||||
'size': member.size,
|
||||
'array_size': member.array_size,
|
||||
})
|
||||
# 计算最大偏移量+大小作为 fallback size
|
||||
member_end = member.offset + member.size
|
||||
if member_end > calculated_size:
|
||||
calculated_size = member_end
|
||||
|
||||
# 使用反射数据中的 size,如果为 0 则使用计算的值
|
||||
final_size = ub.size if ub.size > 0 else calculated_size
|
||||
# 还需要考虑 16 字节对齐
|
||||
final_size = (final_size + 15) & ~15
|
||||
|
||||
uniform_buffers_data.append({
|
||||
'name': ub.name,
|
||||
'struct_name': struct_name,
|
||||
'snake_name': ub.name,
|
||||
'set': ub.set,
|
||||
'binding': ub.binding,
|
||||
'size': final_size,
|
||||
'members': members_data,
|
||||
})
|
||||
|
||||
# 准备 Push Constant 数据
|
||||
push_constants_data = []
|
||||
for pc in reflection.push_constants:
|
||||
struct_name = pc.name # 保持原始名称
|
||||
members_data = []
|
||||
for member in pc.members:
|
||||
cpp_type = SLANG_TO_CPP_TYPE.get(member.type, member.type)
|
||||
members_data.append({
|
||||
'name': member.name, # 保持原始名称
|
||||
'type_name': member.type,
|
||||
'cpp_type': cpp_type,
|
||||
'offset': member.offset,
|
||||
'size': member.size,
|
||||
})
|
||||
push_constants_data.append({
|
||||
'name': pc.name,
|
||||
'struct_name': struct_name,
|
||||
'snake_name': pc.name,
|
||||
'size': pc.size,
|
||||
'stage_flags': pc.stage_flags,
|
||||
'offset': pc.offset,
|
||||
'members': members_data,
|
||||
})
|
||||
|
||||
# 准备采样器数据
|
||||
samplers_data = []
|
||||
for s in reflection.samplers:
|
||||
samplers_data.append({
|
||||
'name': s.name,
|
||||
'snake_name': s.name, # 保持原始名称
|
||||
'set': s.set,
|
||||
'binding': s.binding,
|
||||
'dimension': s.dimension,
|
||||
})
|
||||
|
||||
# 准备绑定常量数据
|
||||
bindings_data = []
|
||||
for ub in reflection.uniform_buffers:
|
||||
bindings_data.append({
|
||||
'const_name': to_upper_snake_case(ub.name),
|
||||
'set': ub.set,
|
||||
'binding': ub.binding,
|
||||
})
|
||||
for s in reflection.samplers:
|
||||
bindings_data.append({
|
||||
'const_name': to_upper_snake_case(s.name),
|
||||
'set': s.set,
|
||||
'binding': s.binding,
|
||||
})
|
||||
|
||||
# 准备每个阶段特有的资源数据
|
||||
stage_resources_data = {}
|
||||
for stage_key, ubs in reflection.stage_uniform_buffers.items():
|
||||
stage_ubs_data = []
|
||||
for ub in ubs:
|
||||
struct_name = ub.name # 保持原始名称
|
||||
members_data = []
|
||||
calculated_size = 0
|
||||
for member in ub.members:
|
||||
cpp_type = SLANG_TO_CPP_TYPE.get(member.type, member.type)
|
||||
alignment = STD140_ALIGNMENT.get(member.type, 16)
|
||||
if member.array_size > 1:
|
||||
cpp_type = f"std::array<{cpp_type}, {member.array_size}>"
|
||||
members_data.append({
|
||||
'name': member.name, # 保持原始名称
|
||||
'type_name': member.type,
|
||||
'cpp_type': cpp_type,
|
||||
'alignment': alignment,
|
||||
'offset': member.offset,
|
||||
'size': member.size,
|
||||
})
|
||||
member_end = member.offset + member.size
|
||||
if member_end > calculated_size:
|
||||
calculated_size = member_end
|
||||
final_size = ub.size if ub.size > 0 else calculated_size
|
||||
final_size = (final_size + 15) & ~15
|
||||
stage_ubs_data.append({
|
||||
'name': ub.name,
|
||||
'struct_name': struct_name,
|
||||
'snake_name': ub.name,
|
||||
'set': ub.set,
|
||||
'binding': ub.binding,
|
||||
'size': final_size,
|
||||
'members': members_data,
|
||||
})
|
||||
stage_resources_data[f"{stage_key}_uniform_buffers"] = stage_ubs_data
|
||||
|
||||
for stage_key, pcs in reflection.stage_push_constants.items():
|
||||
stage_pcs_data = []
|
||||
for pc in pcs:
|
||||
struct_name = pc.name # 保持原始名称
|
||||
members_data = []
|
||||
for member in pc.members:
|
||||
cpp_type = SLANG_TO_CPP_TYPE.get(member.type, member.type)
|
||||
members_data.append({
|
||||
'name': member.name, # 保持原始名称
|
||||
'type_name': member.type,
|
||||
'cpp_type': cpp_type,
|
||||
'offset': member.offset,
|
||||
'size': member.size,
|
||||
})
|
||||
stage_pcs_data.append({
|
||||
'name': pc.name,
|
||||
'struct_name': struct_name,
|
||||
'snake_name': pc.name,
|
||||
'size': pc.size,
|
||||
'stage_flags': pc.stage_flags,
|
||||
'offset': pc.offset,
|
||||
'members': members_data,
|
||||
})
|
||||
stage_resources_data[f"{stage_key}_push_constants"] = stage_pcs_data
|
||||
|
||||
for stage_key, samps in reflection.stage_samplers.items():
|
||||
stage_samps_data = []
|
||||
for s in samps:
|
||||
stage_samps_data.append({
|
||||
'name': s.name,
|
||||
'snake_name': s.name, # 保持原始名称
|
||||
'set': s.set,
|
||||
'binding': s.binding,
|
||||
'dimension': s.dimension,
|
||||
})
|
||||
stage_resources_data[f"{stage_key}_samplers"] = stage_samps_data
|
||||
|
||||
return {
|
||||
'shader_name': shader_name,
|
||||
'snake_name': shader_name,
|
||||
'upper_snake_name': upper_snake_name,
|
||||
'guard': guard,
|
||||
'timestamp': timestamp,
|
||||
'stages': stages_data,
|
||||
'uniform_buffers': uniform_buffers_data,
|
||||
'push_constants': push_constants_data,
|
||||
'samplers': samplers_data,
|
||||
'bindings': bindings_data,
|
||||
'uniform_buffer_count': len(reflection.uniform_buffers),
|
||||
'stage_resources': stage_resources_data,
|
||||
'sampler_count': len(reflection.samplers),
|
||||
# 静态检查所需的元数据
|
||||
'has_uniform_buffers': len(reflection.uniform_buffers) > 0,
|
||||
'has_push_constants': len(reflection.push_constants) > 0,
|
||||
'has_samplers': len(reflection.samplers) > 0,
|
||||
'has_vertex_shader': 'VERT' in stages_data or 'vertex' in stages,
|
||||
'has_fragment_shader': 'FRAG' in stages_data or 'fragment' in stages,
|
||||
}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 代码生成器
|
||||
# ============================================================================
|
||||
|
||||
def create_jinja_env(template_dir: Path) -> Environment:
|
||||
"""创建 Jinja2 环境"""
|
||||
return Environment(
|
||||
loader=FileSystemLoader(template_dir),
|
||||
autoescape=select_autoescape(['html', 'xml']),
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
keep_trailing_newline=True,
|
||||
)
|
||||
|
||||
|
||||
def generate_header(
|
||||
shader_name: str,
|
||||
reflection: ShaderReflection,
|
||||
stages: Dict[str, bytes],
|
||||
template_dir: Path,
|
||||
template_name: str = 'shader_bindings.hpp.j2'
|
||||
) -> str:
|
||||
"""使用 Jinja2 生成 C++ 头文件"""
|
||||
env = create_jinja_env(template_dir)
|
||||
template = env.get_template(template_name)
|
||||
context = prepare_template_context(shader_name, reflection, stages)
|
||||
return template.render(context)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 主函数
|
||||
# ============================================================================
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='MIRAI 着色器绑定代码生成器',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
示例:
|
||||
python generate_shader_bindings.py \\
|
||||
--dir ./shader_intermediate/basic \\
|
||||
--output generated/basic_bindings.hpp \\
|
||||
--name basic
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--dir', '-d',
|
||||
required=True,
|
||||
help='包含 SPIR-V 和反射文件的目录'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--output', '-o',
|
||||
required=True,
|
||||
help='输出的 C++ 头文件路径'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--name', '-n',
|
||||
required=True,
|
||||
help='着色器名称 (用于生成类名和变量名)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--template-dir', '-t',
|
||||
default=None,
|
||||
help='Jinja2 模板目录 (默认: 脚本所在目录的 templates 子目录)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--verbose', '-v',
|
||||
action='store_true',
|
||||
help='输出详细信息'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
# 确定模板
|
||||
template_name = 'widget_bindings.hpp.j2'
|
||||
# 确定模板目录
|
||||
if args.template_dir:
|
||||
template_dir = Path(args.template_dir)
|
||||
else:
|
||||
template_dir = Path(__file__).parent / 'templates'
|
||||
|
||||
if not template_dir.exists():
|
||||
print(f"Error: Template directory not found: {template_dir}")
|
||||
return 1
|
||||
|
||||
# 从目录中查找文件
|
||||
shader_dir = Path(args.dir)
|
||||
if not shader_dir.exists():
|
||||
print(f"Info: Shader directory not found: {shader_dir}. No entry points, skipping.")
|
||||
return 0
|
||||
|
||||
# 查找所有 spv 和 reflect.json 文件
|
||||
spirv_files = sorted(shader_dir.glob("*.spv"))
|
||||
reflect_files = sorted(shader_dir.glob("*.reflect.json"))
|
||||
|
||||
if args.verbose:
|
||||
print(f"Found {len(spirv_files)} SPIR-V files")
|
||||
print(f"Found {len(reflect_files)} reflection files")
|
||||
|
||||
# 如果没有反射文件,跳过生成
|
||||
if not reflect_files:
|
||||
print(f"Info: No reflection files found in {shader_dir}. Skipping binding generation.")
|
||||
return 0
|
||||
|
||||
# 解析反射数据
|
||||
all_reflections = []
|
||||
for reflection_path in reflect_files:
|
||||
if args.verbose:
|
||||
print(f"Parsing reflection: {reflection_path}")
|
||||
reflection = parse_reflection_json(reflection_path)
|
||||
all_reflections.append(reflection)
|
||||
|
||||
# 合并多个反射数据(取并集)
|
||||
reflection = merge_reflections(all_reflections)
|
||||
|
||||
# 加载 SPIR-V 文件
|
||||
stages: Dict[str, bytes] = {}
|
||||
for spirv_path in spirv_files:
|
||||
stage = detect_stage_from_filename(spirv_path.name)
|
||||
if stage is None:
|
||||
if args.verbose:
|
||||
print(f"Warning: Cannot detect shader stage from filename: {spirv_path.name}")
|
||||
stage = spirv_path.stem.split('.')[-1] if '.' in spirv_path.stem else 'unknown'
|
||||
|
||||
if args.verbose:
|
||||
print(f"Loading SPIR-V ({stage}): {spirv_path}")
|
||||
|
||||
stages[stage] = load_spirv(spirv_path)
|
||||
|
||||
# 生成头文件
|
||||
if args.verbose:
|
||||
print(f"Generating header: {args.output}")
|
||||
print(f"Using templates from: {template_dir}")
|
||||
|
||||
header_content = generate_header(args.name, reflection, stages, template_dir, template_name)
|
||||
|
||||
# 确保输出目录存在
|
||||
output_path = Path(args.output)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 写入文件
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(header_content)
|
||||
|
||||
print(f"Generated: {output_path}")
|
||||
|
||||
if args.verbose:
|
||||
print(f" - Stages: {list(stages.keys())}")
|
||||
print(f" - Uniform Buffers: {len(reflection.uniform_buffers)}")
|
||||
print(f" - Samplers: {len(reflection.samplers)}")
|
||||
print(f" - Push Constants: {len(reflection.push_constants)}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
||||
1
tools/requirements.txt
Normal file
1
tools/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
jinja2>=3.0
|
||||
32
tools/shader_compile/CMakeLists.txt
Normal file
32
tools/shader_compile/CMakeLists.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
# tools/shader_compile/CMakeLists.txt
|
||||
# MIRAI 着色器编译器工具
|
||||
# 用于从 SPIR-V 提取反射信息并生成绑定代码
|
||||
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
# 查找 spirv-cross 用于反射
|
||||
find_package(spirv_cross_core CONFIG REQUIRED)
|
||||
find_package(spirv_cross_glsl CONFIG REQUIRED)
|
||||
find_package(spirv_cross_reflect CONFIG REQUIRED)
|
||||
|
||||
# 查找 nlohmann_json 用于反射 JSON 输出
|
||||
find_package(nlohmann_json CONFIG REQUIRED)
|
||||
|
||||
add_executable(mirai_shader_compile
|
||||
main.cpp
|
||||
compiler.cpp
|
||||
)
|
||||
|
||||
target_compile_features(mirai_shader_compile PRIVATE cxx_std_20)
|
||||
|
||||
target_link_libraries(mirai_shader_compile PRIVATE
|
||||
spirv-cross-core
|
||||
spirv-cross-glsl
|
||||
spirv-cross-reflect
|
||||
nlohmann_json::nlohmann_json
|
||||
)
|
||||
|
||||
# 安装到 bin 目录
|
||||
install(TARGETS mirai_shader_compile
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
||||
522
tools/shader_compile/compiler.cpp
Normal file
522
tools/shader_compile/compiler.cpp
Normal file
@@ -0,0 +1,522 @@
|
||||
// tools/shader_compile/compiler.cpp
|
||||
// MIRAI 着色器反射工具实现
|
||||
// 使用 spirv-cross 从 SPIR-V 提取反射信息
|
||||
|
||||
#include "compiler.hpp"
|
||||
|
||||
#include <spirv_cross/spirv_cross.hpp>
|
||||
#include <spirv_cross/spirv_glsl.hpp>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace mirai::tools {
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
std::optional<shader_stage> parse_shader_stage(const std::string& str) {
|
||||
if (str == "vertex" || str == "vert" || str == "vs") {
|
||||
return shader_stage::vertex;
|
||||
}
|
||||
if (str == "fragment" || str == "frag" || str == "pixel" || str == "fs" || str == "ps") {
|
||||
return shader_stage::fragment;
|
||||
}
|
||||
if (str == "compute" || str == "comp" || str == "cs") {
|
||||
return shader_stage::compute;
|
||||
}
|
||||
if (str == "geometry" || str == "geom" || str == "gs") {
|
||||
return shader_stage::geometry;
|
||||
}
|
||||
if (str == "tesscontrol" || str == "tesc" || str == "hull" || str == "hs") {
|
||||
return shader_stage::tessellation_control;
|
||||
}
|
||||
if (str == "tesseval" || str == "tese" || str == "domain" || str == "ds") {
|
||||
return shader_stage::tessellation_evaluation;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const char* shader_stage_to_string(shader_stage stage) {
|
||||
switch (stage) {
|
||||
case shader_stage::vertex: return "vertex";
|
||||
case shader_stage::fragment: return "fragment";
|
||||
case shader_stage::compute: return "compute";
|
||||
case shader_stage::geometry: return "geometry";
|
||||
case shader_stage::tessellation_control: return "tesscontrol";
|
||||
case shader_stage::tessellation_evaluation: return "tesseval";
|
||||
case shader_stage::unknown: return "unknown";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
shader_stage stage_from_extension(const std::string& ext) {
|
||||
std::string ext_lower = ext;
|
||||
for (auto& c : ext_lower) {
|
||||
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
}
|
||||
|
||||
// 处理 .spv 后缀,如 shader.vert.spv
|
||||
if (ext_lower.find(".vert") != std::string::npos) {
|
||||
return shader_stage::vertex;
|
||||
}
|
||||
if (ext_lower.find(".frag") != std::string::npos) {
|
||||
return shader_stage::fragment;
|
||||
}
|
||||
if (ext_lower.find(".comp") != std::string::npos) {
|
||||
return shader_stage::compute;
|
||||
}
|
||||
if (ext_lower.find(".geom") != std::string::npos) {
|
||||
return shader_stage::geometry;
|
||||
}
|
||||
if (ext_lower.find(".tesc") != std::string::npos) {
|
||||
return shader_stage::tessellation_control;
|
||||
}
|
||||
if (ext_lower.find(".tese") != std::string::npos) {
|
||||
return shader_stage::tessellation_evaluation;
|
||||
}
|
||||
|
||||
return shader_stage::unknown;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SPIRV-Cross Type Helpers
|
||||
// ============================================================================
|
||||
|
||||
static std::string spirv_type_to_string(const spirv_cross::Compiler& compiler,
|
||||
const spirv_cross::SPIRType& type) {
|
||||
switch (type.basetype) {
|
||||
case spirv_cross::SPIRType::Float:
|
||||
if (type.columns > 1) {
|
||||
// Matrix
|
||||
return "float" + std::to_string(type.columns) + "x" + std::to_string(type.vecsize);
|
||||
} else if (type.vecsize > 1) {
|
||||
// Vector
|
||||
return "float" + std::to_string(type.vecsize);
|
||||
}
|
||||
return "float";
|
||||
|
||||
case spirv_cross::SPIRType::Int:
|
||||
if (type.vecsize > 1) {
|
||||
return "int" + std::to_string(type.vecsize);
|
||||
}
|
||||
return "int";
|
||||
|
||||
case spirv_cross::SPIRType::UInt:
|
||||
if (type.vecsize > 1) {
|
||||
return "uint" + std::to_string(type.vecsize);
|
||||
}
|
||||
return "uint";
|
||||
|
||||
case spirv_cross::SPIRType::Boolean:
|
||||
if (type.vecsize > 1) {
|
||||
return "bool" + std::to_string(type.vecsize);
|
||||
}
|
||||
return "bool";
|
||||
|
||||
case spirv_cross::SPIRType::Double:
|
||||
if (type.columns > 1) {
|
||||
return "double" + std::to_string(type.columns) + "x" + std::to_string(type.vecsize);
|
||||
} else if (type.vecsize > 1) {
|
||||
return "double" + std::to_string(type.vecsize);
|
||||
}
|
||||
return "double";
|
||||
|
||||
case spirv_cross::SPIRType::Struct:
|
||||
return compiler.get_name(type.self);
|
||||
|
||||
case spirv_cross::SPIRType::SampledImage:
|
||||
case spirv_cross::SPIRType::Image:
|
||||
return "sampler";
|
||||
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string image_dim_to_string(spv::Dim dim) {
|
||||
switch (dim) {
|
||||
case spv::Dim1D: return "1D";
|
||||
case spv::Dim2D: return "2D";
|
||||
case spv::Dim3D: return "3D";
|
||||
case spv::DimCube: return "Cube";
|
||||
case spv::DimBuffer: return "Buffer";
|
||||
default: return "2D";
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// spirv_reflector Implementation
|
||||
// ============================================================================
|
||||
|
||||
reflection_result spirv_reflector::reflect_file(const std::filesystem::path& path) {
|
||||
reflection_result result;
|
||||
|
||||
// 读取 SPIR-V 文件
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
result.error_message = "Failed to open file: " + path.string();
|
||||
return result;
|
||||
}
|
||||
|
||||
auto file_size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
if (file_size % sizeof(uint32_t) != 0) {
|
||||
result.error_message = "Invalid SPIR-V file size: " + path.string();
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> spirv(file_size / sizeof(uint32_t));
|
||||
file.read(reinterpret_cast<char*>(spirv.data()), file_size);
|
||||
|
||||
if (!file) {
|
||||
result.error_message = "Failed to read file: " + path.string();
|
||||
return result;
|
||||
}
|
||||
|
||||
auto res = reflect_spirv(spirv, path.filename().string());
|
||||
res.source_filename = path.filename().string();
|
||||
res.spirv_data = std::move(spirv);
|
||||
return res;
|
||||
}
|
||||
|
||||
reflection_result spirv_reflector::reflect_spirv(
|
||||
const std::vector<uint32_t>& spirv,
|
||||
const std::string& filename
|
||||
) {
|
||||
reflection_result result;
|
||||
|
||||
try {
|
||||
spirv_cross::Compiler compiler(spirv);
|
||||
spirv_cross::ShaderResources resources = compiler.get_shader_resources();
|
||||
|
||||
// 获取入口点
|
||||
auto entry_points = compiler.get_entry_points_and_stages();
|
||||
if (!entry_points.empty()) {
|
||||
result.reflection.entry_point = entry_points[0].name;
|
||||
|
||||
// 从 SPIR-V 执行模型推断阶段
|
||||
switch (entry_points[0].execution_model) {
|
||||
case spv::ExecutionModelVertex:
|
||||
result.reflection.stage = shader_stage::vertex;
|
||||
break;
|
||||
case spv::ExecutionModelFragment:
|
||||
result.reflection.stage = shader_stage::fragment;
|
||||
break;
|
||||
case spv::ExecutionModelGLCompute:
|
||||
result.reflection.stage = shader_stage::compute;
|
||||
break;
|
||||
case spv::ExecutionModelGeometry:
|
||||
result.reflection.stage = shader_stage::geometry;
|
||||
break;
|
||||
case spv::ExecutionModelTessellationControl:
|
||||
result.reflection.stage = shader_stage::tessellation_control;
|
||||
break;
|
||||
case spv::ExecutionModelTessellationEvaluation:
|
||||
result.reflection.stage = shader_stage::tessellation_evaluation;
|
||||
break;
|
||||
default:
|
||||
result.reflection.stage = shader_stage::unknown;
|
||||
break;
|
||||
}
|
||||
} else if (!filename.empty()) {
|
||||
// 从文件名推断阶段
|
||||
result.reflection.stage = stage_from_extension(filename);
|
||||
}
|
||||
|
||||
// 处理 Uniform Buffers
|
||||
for (const auto& ub : resources.uniform_buffers) {
|
||||
uniform_buffer_info info;
|
||||
info.name = compiler.get_name(ub.id);
|
||||
if (info.name.empty()) {
|
||||
info.name = compiler.get_fallback_name(ub.id);
|
||||
}
|
||||
|
||||
info.set = compiler.get_decoration(ub.id, spv::DecorationDescriptorSet);
|
||||
info.binding = compiler.get_decoration(ub.id, spv::DecorationBinding);
|
||||
|
||||
const auto& type = compiler.get_type(ub.base_type_id);
|
||||
info.size = static_cast<uint32_t>(compiler.get_declared_struct_size(type));
|
||||
|
||||
// 获取成员信息
|
||||
for (uint32_t i = 0; i < type.member_types.size(); ++i) {
|
||||
uniform_member member;
|
||||
member.name = compiler.get_member_name(type.self, i);
|
||||
|
||||
const auto& member_type = compiler.get_type(type.member_types[i]);
|
||||
member.type = spirv_type_to_string(compiler, member_type);
|
||||
member.offset = compiler.type_struct_member_offset(type, i);
|
||||
member.size = static_cast<uint32_t>(compiler.get_declared_struct_member_size(type, i));
|
||||
|
||||
if (!member_type.array.empty()) {
|
||||
member.array_size = member_type.array[0];
|
||||
}
|
||||
|
||||
info.members.push_back(member);
|
||||
}
|
||||
|
||||
result.reflection.uniform_buffers.push_back(info);
|
||||
}
|
||||
|
||||
// 处理 Push Constants
|
||||
for (const auto& pc : resources.push_constant_buffers) {
|
||||
push_constant_info info;
|
||||
info.name = compiler.get_name(pc.id);
|
||||
if (info.name.empty()) {
|
||||
info.name = "PushConstants";
|
||||
}
|
||||
|
||||
const auto& type = compiler.get_type(pc.base_type_id);
|
||||
info.size = static_cast<uint32_t>(compiler.get_declared_struct_size(type));
|
||||
|
||||
// 获取成员信息
|
||||
for (uint32_t i = 0; i < type.member_types.size(); ++i) {
|
||||
uniform_member member;
|
||||
member.name = compiler.get_member_name(type.self, i);
|
||||
|
||||
const auto& member_type = compiler.get_type(type.member_types[i]);
|
||||
member.type = spirv_type_to_string(compiler, member_type);
|
||||
member.offset = compiler.type_struct_member_offset(type, i);
|
||||
member.size = static_cast<uint32_t>(compiler.get_declared_struct_member_size(type, i));
|
||||
|
||||
if (!member_type.array.empty()) {
|
||||
member.array_size = member_type.array[0];
|
||||
}
|
||||
|
||||
info.members.push_back(member);
|
||||
}
|
||||
|
||||
result.reflection.push_constants.push_back(info);
|
||||
}
|
||||
|
||||
// 处理采样器/纹理
|
||||
for (const auto& img : resources.sampled_images) {
|
||||
sampler_info info;
|
||||
info.name = compiler.get_name(img.id);
|
||||
if (info.name.empty()) {
|
||||
info.name = compiler.get_fallback_name(img.id);
|
||||
}
|
||||
|
||||
info.set = compiler.get_decoration(img.id, spv::DecorationDescriptorSet);
|
||||
info.binding = compiler.get_decoration(img.id, spv::DecorationBinding);
|
||||
|
||||
const auto& type = compiler.get_type(img.type_id);
|
||||
info.dimension = image_dim_to_string(type.image.dim);
|
||||
info.is_array = type.image.arrayed;
|
||||
|
||||
result.reflection.samplers.push_back(info);
|
||||
}
|
||||
|
||||
// 处理独立纹理
|
||||
for (const auto& img : resources.separate_images) {
|
||||
sampler_info info;
|
||||
info.name = compiler.get_name(img.id);
|
||||
if (info.name.empty()) {
|
||||
info.name = compiler.get_fallback_name(img.id);
|
||||
}
|
||||
|
||||
info.set = compiler.get_decoration(img.id, spv::DecorationDescriptorSet);
|
||||
info.binding = compiler.get_decoration(img.id, spv::DecorationBinding);
|
||||
|
||||
const auto& type = compiler.get_type(img.type_id);
|
||||
info.dimension = image_dim_to_string(type.image.dim);
|
||||
info.is_array = type.image.arrayed;
|
||||
|
||||
result.reflection.samplers.push_back(info);
|
||||
}
|
||||
|
||||
// 处理顶点输入(仅顶点着色器)
|
||||
if (result.reflection.stage == shader_stage::vertex) {
|
||||
for (const auto& input : resources.stage_inputs) {
|
||||
vertex_attribute_info attr;
|
||||
attr.name = compiler.get_name(input.id);
|
||||
if (attr.name.empty()) {
|
||||
attr.name = compiler.get_fallback_name(input.id);
|
||||
}
|
||||
|
||||
attr.location = compiler.get_decoration(input.id, spv::DecorationLocation);
|
||||
|
||||
const auto& type = compiler.get_type(input.type_id);
|
||||
attr.type = spirv_type_to_string(compiler, type);
|
||||
|
||||
result.reflection.vertex_inputs.push_back(attr);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理片段输出(仅片段着色器)
|
||||
if (result.reflection.stage == shader_stage::fragment) {
|
||||
for (const auto& output : resources.stage_outputs) {
|
||||
vertex_attribute_info attr;
|
||||
attr.name = compiler.get_name(output.id);
|
||||
if (attr.name.empty()) {
|
||||
attr.name = compiler.get_fallback_name(output.id);
|
||||
}
|
||||
|
||||
attr.location = compiler.get_decoration(output.id, spv::DecorationLocation);
|
||||
|
||||
const auto& type = compiler.get_type(output.type_id);
|
||||
attr.type = spirv_type_to_string(compiler, type);
|
||||
|
||||
result.reflection.fragment_outputs.push_back(attr);
|
||||
}
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
result.reflection_json = reflection_to_json(result.reflection);
|
||||
|
||||
} catch (const spirv_cross::CompilerError& e) {
|
||||
result.error_message = std::string("SPIRV-Cross error: ") + e.what();
|
||||
} catch (const std::exception& e) {
|
||||
result.error_message = std::string("Error: ") + e.what();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<reflection_result> spirv_reflector::reflect_directory(
|
||||
const std::filesystem::path& dir
|
||||
) {
|
||||
std::vector<reflection_result> results;
|
||||
|
||||
if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) {
|
||||
return results;
|
||||
}
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dir)) {
|
||||
if (!entry.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto ext = entry.path().extension().string();
|
||||
if (ext == ".spv") {
|
||||
auto result = reflect_file(entry.path());
|
||||
results.push_back(result);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// JSON Serialization
|
||||
// ============================================================================
|
||||
|
||||
std::string reflection_to_json(const shader_reflection& reflection) {
|
||||
nlohmann::json root;
|
||||
|
||||
root["entryPoint"] = reflection.entry_point;
|
||||
root["stage"] = shader_stage_to_string(reflection.stage);
|
||||
|
||||
// Uniform Buffers
|
||||
nlohmann::json uniform_buffers = nlohmann::json::array();
|
||||
for (const auto& ub : reflection.uniform_buffers) {
|
||||
nlohmann::json ub_json;
|
||||
ub_json["name"] = ub.name;
|
||||
ub_json["set"] = ub.set;
|
||||
ub_json["binding"] = ub.binding;
|
||||
ub_json["size"] = ub.size;
|
||||
|
||||
nlohmann::json members = nlohmann::json::array();
|
||||
for (const auto& member : ub.members) {
|
||||
nlohmann::json member_json;
|
||||
member_json["name"] = member.name;
|
||||
member_json["type"] = member.type;
|
||||
member_json["offset"] = member.offset;
|
||||
member_json["size"] = member.size;
|
||||
if (member.array_size > 0) {
|
||||
member_json["arraySize"] = member.array_size;
|
||||
}
|
||||
members.push_back(member_json);
|
||||
}
|
||||
ub_json["members"] = members;
|
||||
|
||||
uniform_buffers.push_back(ub_json);
|
||||
}
|
||||
root["uniformBuffers"] = uniform_buffers;
|
||||
|
||||
// Push Constants
|
||||
nlohmann::json push_constants = nlohmann::json::array();
|
||||
for (const auto& pc : reflection.push_constants) {
|
||||
nlohmann::json pc_json;
|
||||
pc_json["name"] = pc.name;
|
||||
pc_json["size"] = pc.size;
|
||||
|
||||
nlohmann::json members = nlohmann::json::array();
|
||||
for (const auto& member : pc.members) {
|
||||
nlohmann::json member_json;
|
||||
member_json["name"] = member.name;
|
||||
member_json["type"] = member.type;
|
||||
member_json["offset"] = member.offset;
|
||||
member_json["size"] = member.size;
|
||||
if (member.array_size > 0) {
|
||||
member_json["arraySize"] = member.array_size;
|
||||
}
|
||||
members.push_back(member_json);
|
||||
}
|
||||
pc_json["members"] = members;
|
||||
|
||||
push_constants.push_back(pc_json);
|
||||
}
|
||||
root["pushConstants"] = push_constants;
|
||||
|
||||
// Samplers
|
||||
nlohmann::json samplers = nlohmann::json::array();
|
||||
for (const auto& sampler : reflection.samplers) {
|
||||
nlohmann::json sampler_json;
|
||||
sampler_json["name"] = sampler.name;
|
||||
sampler_json["set"] = sampler.set;
|
||||
sampler_json["binding"] = sampler.binding;
|
||||
sampler_json["dimension"] = sampler.dimension;
|
||||
if (sampler.is_array) {
|
||||
sampler_json["isArray"] = true;
|
||||
}
|
||||
samplers.push_back(sampler_json);
|
||||
}
|
||||
root["samplers"] = samplers;
|
||||
|
||||
// Vertex Inputs
|
||||
if (!reflection.vertex_inputs.empty()) {
|
||||
nlohmann::json inputs = nlohmann::json::array();
|
||||
for (const auto& input : reflection.vertex_inputs) {
|
||||
nlohmann::json input_json;
|
||||
input_json["name"] = input.name;
|
||||
input_json["location"] = input.location;
|
||||
input_json["type"] = input.type;
|
||||
inputs.push_back(input_json);
|
||||
}
|
||||
root["vertexInputs"] = inputs;
|
||||
}
|
||||
|
||||
// Fragment Outputs
|
||||
if (!reflection.fragment_outputs.empty()) {
|
||||
nlohmann::json outputs = nlohmann::json::array();
|
||||
for (const auto& output : reflection.fragment_outputs) {
|
||||
nlohmann::json output_json;
|
||||
output_json["name"] = output.name;
|
||||
output_json["location"] = output.location;
|
||||
output_json["type"] = output.type;
|
||||
outputs.push_back(output_json);
|
||||
}
|
||||
root["fragmentOutputs"] = outputs;
|
||||
}
|
||||
|
||||
return root.dump(2);
|
||||
}
|
||||
|
||||
std::string reflections_to_json(const std::vector<shader_reflection>& reflections) {
|
||||
nlohmann::json root;
|
||||
root["shaders"] = nlohmann::json::array();
|
||||
|
||||
for (const auto& reflection : reflections) {
|
||||
root["shaders"].push_back(nlohmann::json::parse(reflection_to_json(reflection)));
|
||||
}
|
||||
|
||||
return root.dump(2);
|
||||
}
|
||||
|
||||
} // namespace mirai::tools
|
||||
175
tools/shader_compile/compiler.hpp
Normal file
175
tools/shader_compile/compiler.hpp
Normal file
@@ -0,0 +1,175 @@
|
||||
// tools/shader_compile/compiler.hpp
|
||||
// MIRAI 着色器反射工具
|
||||
// 使用 spirv-cross 从 SPIR-V 提取反射信息
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace mirai::tools {
|
||||
|
||||
/**
|
||||
* @brief 着色器阶段
|
||||
*/
|
||||
enum class shader_stage {
|
||||
vertex,
|
||||
fragment,
|
||||
compute,
|
||||
geometry,
|
||||
tessellation_control,
|
||||
tessellation_evaluation,
|
||||
unknown
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 将字符串转换为着色器阶段
|
||||
*/
|
||||
[[nodiscard]] std::optional<shader_stage> parse_shader_stage(const std::string& str);
|
||||
|
||||
/**
|
||||
* @brief 将着色器阶段转换为字符串
|
||||
*/
|
||||
[[nodiscard]] const char* shader_stage_to_string(shader_stage stage);
|
||||
|
||||
/**
|
||||
* @brief 从文件扩展名推断着色器阶段
|
||||
*/
|
||||
[[nodiscard]] shader_stage stage_from_extension(const std::string& ext);
|
||||
|
||||
/**
|
||||
* @brief Uniform Buffer 成员信息
|
||||
*/
|
||||
struct uniform_member {
|
||||
std::string name;
|
||||
std::string type;
|
||||
uint32_t offset{0};
|
||||
uint32_t size{0};
|
||||
uint32_t array_size{0}; // 0 表示非数组
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Uniform Buffer 信息
|
||||
*/
|
||||
struct uniform_buffer_info {
|
||||
std::string name;
|
||||
uint32_t set{0};
|
||||
uint32_t binding{0};
|
||||
uint32_t size{0};
|
||||
std::vector<uniform_member> members;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Push Constant 信息
|
||||
*/
|
||||
struct push_constant_info {
|
||||
std::string name;
|
||||
uint32_t size{0};
|
||||
std::vector<uniform_member> members;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 采样器/纹理信息
|
||||
*/
|
||||
struct sampler_info {
|
||||
std::string name;
|
||||
uint32_t set{0};
|
||||
uint32_t binding{0};
|
||||
std::string dimension; // "1D", "2D", "3D", "Cube"
|
||||
bool is_array{false};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 顶点输入属性信息
|
||||
*/
|
||||
struct vertex_attribute_info {
|
||||
std::string name;
|
||||
uint32_t location{0};
|
||||
std::string type;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 着色器反射数据
|
||||
*/
|
||||
struct shader_reflection {
|
||||
std::string entry_point{"main"};
|
||||
shader_stage stage{shader_stage::unknown};
|
||||
std::vector<uniform_buffer_info> uniform_buffers;
|
||||
std::vector<push_constant_info> push_constants;
|
||||
std::vector<sampler_info> samplers;
|
||||
std::vector<vertex_attribute_info> vertex_inputs;
|
||||
std::vector<vertex_attribute_info> fragment_outputs;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SPIR-V 反射结果
|
||||
*/
|
||||
struct reflection_result {
|
||||
bool success{false};
|
||||
shader_reflection reflection;
|
||||
std::string reflection_json;
|
||||
std::string error_message;
|
||||
std::string source_filename; // 源文件名
|
||||
std::vector<uint32_t> spirv_data; // SPIR-V 字节码
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SPIR-V 反射器
|
||||
*
|
||||
* 使用 spirv-cross 从 SPIR-V 字节码提取反射信息
|
||||
*/
|
||||
class spirv_reflector {
|
||||
public:
|
||||
spirv_reflector() = default;
|
||||
~spirv_reflector() = default;
|
||||
|
||||
// 禁止拷贝
|
||||
spirv_reflector(const spirv_reflector&) = delete;
|
||||
spirv_reflector& operator=(const spirv_reflector&) = delete;
|
||||
|
||||
// 允许移动
|
||||
spirv_reflector(spirv_reflector&&) noexcept = default;
|
||||
spirv_reflector& operator=(spirv_reflector&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief 从 SPIR-V 文件提取反射信息
|
||||
* @param path SPIR-V 文件路径
|
||||
* @return 反射结果
|
||||
*/
|
||||
[[nodiscard]] reflection_result reflect_file(const std::filesystem::path& path);
|
||||
|
||||
/**
|
||||
* @brief 从 SPIR-V 字节码提取反射信息
|
||||
* @param spirv SPIR-V 字节码
|
||||
* @param filename 文件名(用于错误报告和阶段推断)
|
||||
* @return 反射结果
|
||||
*/
|
||||
[[nodiscard]] reflection_result reflect_spirv(
|
||||
const std::vector<uint32_t>& spirv,
|
||||
const std::string& filename = ""
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 从目录中的所有 SPIR-V 文件提取反射信息
|
||||
* @param dir 目录路径
|
||||
* @return 反射结果列表
|
||||
*/
|
||||
[[nodiscard]] std::vector<reflection_result> reflect_directory(
|
||||
const std::filesystem::path& dir
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 将反射数据转换为 JSON 字符串
|
||||
*/
|
||||
[[nodiscard]] std::string reflection_to_json(const shader_reflection& reflection);
|
||||
|
||||
/**
|
||||
* @brief 将多个反射数据合并为一个 JSON 字符串
|
||||
*/
|
||||
[[nodiscard]] std::string reflections_to_json(const std::vector<shader_reflection>& reflections);
|
||||
|
||||
} // namespace mirai::tools
|
||||
368
tools/shader_compile/main.cpp
Normal file
368
tools/shader_compile/main.cpp
Normal file
@@ -0,0 +1,368 @@
|
||||
// tools/shader_compile/main.cpp
|
||||
// MIRAI 着色器反射工具命令行入口
|
||||
// 从 SPIR-V 文件提取反射信息并生成绑定代码
|
||||
|
||||
#include "compiler.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
void print_usage(const char* program_name) {
|
||||
std::cout << R"(
|
||||
MIRAI Shader Reflection Tool
|
||||
|
||||
Usage: )" << program_name << R"( [options]
|
||||
|
||||
Options:
|
||||
--dir <path> Directory containing SPIR-V files
|
||||
--file <path> Single SPIR-V file to process
|
||||
--output <path> Output file path (header or JSON)
|
||||
--name <name> Shader group name (for header generation)
|
||||
--json Output JSON reflection data
|
||||
--header Output C++ header with bindings (default)
|
||||
-h, --help Show this help
|
||||
-v, --version Show version info
|
||||
|
||||
Examples:
|
||||
)" << program_name << R"( --dir ./shaders --output bindings.hpp --name my_shader
|
||||
)" << program_name << R"( --file shader.vert.spv --output shader.json --json
|
||||
)" << program_name << R"( --dir ./compiled --output reflection.json --json
|
||||
|
||||
Output Formats:
|
||||
Header (.hpp): C++ header with embedded SPIR-V and reflection data
|
||||
JSON (.json): Structured reflection data for external tools
|
||||
|
||||
)";
|
||||
}
|
||||
|
||||
void print_version() {
|
||||
std::cout << "MIRAI Shader Reflection Tool v1.0.0\n";
|
||||
std::cout << "Based on SPIRV-Cross\n";
|
||||
}
|
||||
|
||||
struct command_line_args {
|
||||
std::filesystem::path input_dir;
|
||||
std::filesystem::path input_file;
|
||||
std::filesystem::path output_path;
|
||||
std::string name;
|
||||
bool output_json = false;
|
||||
bool output_header = true;
|
||||
bool show_help = false;
|
||||
bool show_version = false;
|
||||
};
|
||||
|
||||
bool parse_args(int argc, char* argv[], command_line_args& args) {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
|
||||
if (arg == "-h" || arg == "--help") {
|
||||
args.show_help = true;
|
||||
return true;
|
||||
}
|
||||
if (arg == "-v" || arg == "--version") {
|
||||
args.show_version = true;
|
||||
return true;
|
||||
}
|
||||
if (arg == "--dir") {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: --dir requires an argument\n";
|
||||
return false;
|
||||
}
|
||||
args.input_dir = argv[i];
|
||||
}
|
||||
else if (arg == "--file") {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: --file requires an argument\n";
|
||||
return false;
|
||||
}
|
||||
args.input_file = argv[i];
|
||||
}
|
||||
else if (arg == "--output" || arg == "-o") {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: --output requires an argument\n";
|
||||
return false;
|
||||
}
|
||||
args.output_path = argv[i];
|
||||
}
|
||||
else if (arg == "--name") {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: --name requires an argument\n";
|
||||
return false;
|
||||
}
|
||||
args.name = argv[i];
|
||||
}
|
||||
else if (arg == "--json") {
|
||||
args.output_json = true;
|
||||
args.output_header = false;
|
||||
}
|
||||
else if (arg == "--header") {
|
||||
args.output_header = true;
|
||||
args.output_json = false;
|
||||
}
|
||||
else {
|
||||
std::cerr << "Error: Unknown option: " << arg << "\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool write_text(const std::filesystem::path& path, const std::string& text) {
|
||||
std::ofstream file(path);
|
||||
if (!file) {
|
||||
std::cerr << "Error: Failed to open output file: " << path << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
file << text;
|
||||
return file.good();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> read_binary_file(const std::filesystem::path& path) {
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<uint8_t> data(size);
|
||||
file.read(reinterpret_cast<char*>(data.data()), size);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
std::string generate_header(
|
||||
const std::string& name,
|
||||
const std::vector<mirai::tools::reflection_result>& results
|
||||
) {
|
||||
std::ostringstream ss;
|
||||
|
||||
// Header guard
|
||||
std::string guard_name = name;
|
||||
for (auto& c : guard_name) {
|
||||
c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
|
||||
}
|
||||
|
||||
ss << "// Auto-generated shader bindings for: " << name << "\n";
|
||||
ss << "// DO NOT EDIT - Generated by mirai_shader_compile\n\n";
|
||||
ss << "#pragma once\n\n";
|
||||
ss << "#include <array>\n";
|
||||
ss << "#include <cstdint>\n";
|
||||
ss << "#include <span>\n";
|
||||
ss << "#include <string_view>\n\n";
|
||||
ss << "namespace mirai::shaders {\n\n";
|
||||
ss << "namespace " << name << " {\n\n";
|
||||
|
||||
// Generate SPIR-V data for each shader
|
||||
for (const auto& result : results) {
|
||||
if (!result.success) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& reflection = result.reflection;
|
||||
std::string stage_name = mirai::tools::shader_stage_to_string(reflection.stage);
|
||||
|
||||
ss << "// " << stage_name << " shader\n";
|
||||
ss << "namespace " << stage_name << " {\n\n";
|
||||
|
||||
ss << "constexpr std::string_view entry_point = \"" << reflection.entry_point << "\";\n\n";
|
||||
|
||||
// Embed SPIR-V bytecode
|
||||
if (!result.spirv_data.empty()) {
|
||||
ss << "// SPIR-V bytecode (" << result.spirv_data.size() << " words, "
|
||||
<< (result.spirv_data.size() * 4) << " bytes)\n";
|
||||
ss << "inline constexpr std::array<uint32_t, " << result.spirv_data.size() << "> spirv_code = {\n";
|
||||
|
||||
// Write SPIR-V data in rows of 8 values
|
||||
for (size_t i = 0; i < result.spirv_data.size(); ++i) {
|
||||
if (i % 8 == 0) {
|
||||
ss << " ";
|
||||
}
|
||||
ss << "0x" << std::hex << std::setfill('0') << std::setw(8) << result.spirv_data[i];
|
||||
if (i + 1 < result.spirv_data.size()) {
|
||||
ss << ",";
|
||||
}
|
||||
if ((i + 1) % 8 == 0 || i + 1 == result.spirv_data.size()) {
|
||||
ss << "\n";
|
||||
} else {
|
||||
ss << " ";
|
||||
}
|
||||
}
|
||||
ss << std::dec; // Reset to decimal
|
||||
ss << "};\n\n";
|
||||
|
||||
// Provide a span view for easy access
|
||||
ss << "inline constexpr std::span<const uint32_t> spirv() {\n";
|
||||
ss << " return spirv_code;\n";
|
||||
ss << "}\n\n";
|
||||
|
||||
// Provide byte view
|
||||
ss << "inline const uint8_t* spirv_bytes() {\n";
|
||||
ss << " return reinterpret_cast<const uint8_t*>(spirv_code.data());\n";
|
||||
ss << "}\n\n";
|
||||
|
||||
ss << "inline constexpr size_t spirv_size() {\n";
|
||||
ss << " return spirv_code.size() * sizeof(uint32_t);\n";
|
||||
ss << "}\n\n";
|
||||
}
|
||||
|
||||
// Uniform buffer info
|
||||
if (!reflection.uniform_buffers.empty()) {
|
||||
ss << "// Uniform Buffers\n";
|
||||
for (const auto& ub : reflection.uniform_buffers) {
|
||||
ss << "struct " << ub.name << " {\n";
|
||||
ss << " static constexpr uint32_t set = " << ub.set << ";\n";
|
||||
ss << " static constexpr uint32_t binding = " << ub.binding << ";\n";
|
||||
ss << " static constexpr uint32_t size = " << ub.size << ";\n";
|
||||
ss << "};\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Push constant info
|
||||
if (!reflection.push_constants.empty()) {
|
||||
ss << "// Push Constants\n";
|
||||
for (const auto& pc : reflection.push_constants) {
|
||||
ss << "struct " << pc.name << " {\n";
|
||||
ss << " static constexpr uint32_t size = " << pc.size << ";\n";
|
||||
ss << "};\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Sampler info
|
||||
if (!reflection.samplers.empty()) {
|
||||
ss << "// Samplers\n";
|
||||
for (const auto& sampler : reflection.samplers) {
|
||||
ss << "struct " << sampler.name << "_info {\n";
|
||||
ss << " static constexpr uint32_t set = " << sampler.set << ";\n";
|
||||
ss << " static constexpr uint32_t binding = " << sampler.binding << ";\n";
|
||||
ss << " static constexpr std::string_view dimension = \"" << sampler.dimension << "\";\n";
|
||||
ss << "};\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
ss << "} // namespace " << stage_name << "\n\n";
|
||||
}
|
||||
|
||||
// Reflection JSON
|
||||
ss << "// Combined reflection data\n";
|
||||
ss << "constexpr std::string_view reflection_json = R\"JSON(\n";
|
||||
|
||||
std::vector<mirai::tools::shader_reflection> reflections;
|
||||
for (const auto& result : results) {
|
||||
if (result.success) {
|
||||
reflections.push_back(result.reflection);
|
||||
}
|
||||
}
|
||||
ss << mirai::tools::reflections_to_json(reflections);
|
||||
ss << "\n)JSON\";\n\n";
|
||||
|
||||
ss << "} // namespace " << name << "\n\n";
|
||||
ss << "} // namespace mirai::shaders\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
command_line_args args;
|
||||
|
||||
if (!parse_args(argc, argv, args)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (args.show_help) {
|
||||
print_usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (args.show_version) {
|
||||
print_version();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (args.input_dir.empty() && args.input_file.empty()) {
|
||||
std::cerr << "Error: No input specified. Use --dir or --file.\n";
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (args.output_path.empty()) {
|
||||
std::cerr << "Error: No output path specified. Use --output.\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create reflector
|
||||
mirai::tools::spirv_reflector reflector;
|
||||
std::vector<mirai::tools::reflection_result> results;
|
||||
|
||||
// Process input
|
||||
if (!args.input_dir.empty()) {
|
||||
results = reflector.reflect_directory(args.input_dir);
|
||||
} else if (!args.input_file.empty()) {
|
||||
auto result = reflector.reflect_file(args.input_file);
|
||||
results.push_back(result);
|
||||
}
|
||||
|
||||
if (results.empty()) {
|
||||
std::cout << "Warning: No SPIR-V files found to process.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check for errors
|
||||
int error_count = 0;
|
||||
for (const auto& result : results) {
|
||||
if (!result.success) {
|
||||
std::cerr << "Error: " << result.error_message << "\n";
|
||||
error_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate output
|
||||
std::string output;
|
||||
|
||||
if (args.output_json) {
|
||||
std::vector<mirai::tools::shader_reflection> reflections;
|
||||
for (const auto& result : results) {
|
||||
if (result.success) {
|
||||
reflections.push_back(result.reflection);
|
||||
}
|
||||
}
|
||||
output = mirai::tools::reflections_to_json(reflections);
|
||||
} else {
|
||||
// Header output
|
||||
if (args.name.empty()) {
|
||||
// Try to derive name from output path
|
||||
args.name = args.output_path.stem().string();
|
||||
// Remove _bindings suffix if present
|
||||
if (args.name.ends_with("_bindings")) {
|
||||
args.name = args.name.substr(0, args.name.length() - 9);
|
||||
}
|
||||
}
|
||||
output = generate_header(args.name, results);
|
||||
}
|
||||
|
||||
// Write output
|
||||
if (!write_text(args.output_path, output)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Generated: " << args.output_path << "\n";
|
||||
|
||||
int success_count = static_cast<int>(results.size()) - error_count;
|
||||
std::cout << "Processed " << success_count << " shader(s)";
|
||||
if (error_count > 0) {
|
||||
std::cout << " (" << error_count << " error(s))";
|
||||
}
|
||||
std::cout << "\n";
|
||||
|
||||
return error_count > 0 ? 1 : 0;
|
||||
}
|
||||
325
tools/templates/shader_bindings.hpp.j2
Normal file
325
tools/templates/shader_bindings.hpp.j2
Normal file
@@ -0,0 +1,325 @@
|
||||
/**
|
||||
* @file {{ snake_name }}_bindings.hpp
|
||||
* @brief 自动生成的着色器绑定 - 请勿手动修改
|
||||
* @generated by generate_shader_bindings.py at {{ timestamp }}
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
#include <Eigen/Core>
|
||||
#include <shader_types.hpp>
|
||||
|
||||
namespace mirai::generated {
|
||||
|
||||
// ============================================================================
|
||||
// SPIR-V 字节码
|
||||
// ============================================================================
|
||||
{% for stage_name, spirv_data in stages.items() %}
|
||||
|
||||
inline constexpr std::array<uint32_t, {{ spirv_data.word_count }}> {{ upper_snake_name }}_{{ stage_name | upper }}_SPIRV = {
|
||||
{% for line in spirv_data.hex_lines %}
|
||||
{{ line }}
|
||||
{% endfor %}
|
||||
};
|
||||
{% endfor %}
|
||||
|
||||
{% if uniform_buffers %}
|
||||
// ============================================================================
|
||||
// Uniform 结构体 (合并后的全局视图)
|
||||
// ============================================================================
|
||||
{% for ub in uniform_buffers %}
|
||||
|
||||
/**
|
||||
* @brief {{ ub.struct_name }} Uniform Buffer
|
||||
* @details Set: {{ ub.set }}, Binding: {{ ub.binding }}, Size: {{ ub.size }} bytes
|
||||
*/
|
||||
struct alignas(16) {{ ub.struct_name }} {
|
||||
{% for member in ub.members %}
|
||||
alignas({{ member.alignment }}) {{ member.cpp_type }} {{ member.name }};
|
||||
{% endfor %}
|
||||
};
|
||||
static_assert(sizeof({{ ub.struct_name }}) == {{ ub.size }}, "{{ ub.struct_name }} size mismatch");
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if stage_resources %}
|
||||
// ============================================================================
|
||||
// 按阶段划分的 Uniform 结构体
|
||||
// ============================================================================
|
||||
{% for stage_key in ['VERT', 'FRAG', 'COMP', 'GEOM', 'TESC', 'TESE'] %}
|
||||
{% set key = stage_key.lower() + '_uniform_buffers' %}
|
||||
{% set stage_prefix = stage_key %}
|
||||
{% set ubs = stage_resources.get(key, []) %}
|
||||
{% if ubs %}
|
||||
// -------------------------- {{ stage_prefix }} 阶段 --------------------------
|
||||
{% for ub in ubs %}
|
||||
/**
|
||||
* @brief {{ ub.name }} ({{ stage_prefix }})
|
||||
* @details Set: {{ ub.set }}, Binding: {{ ub.binding }}, Size: {{ ub.size }} bytes
|
||||
*/
|
||||
struct alignas(16) {{ ub.name }}_{{ stage_prefix }} {
|
||||
{% for member in ub.members %}
|
||||
alignas({{ member.alignment }}) {{ member.cpp_type }} {{ member.name }};
|
||||
{% endfor %}
|
||||
};
|
||||
static_assert(sizeof({{ ub.name }}_{{ stage_prefix }}) == {{ ub.size }}, "{{ ub.name }}_{{ stage_prefix }} size mismatch");
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if push_constants %}
|
||||
// ============================================================================
|
||||
// Push Constant 结构体 (合并后的全局视图)
|
||||
// ============================================================================
|
||||
{% for pc in push_constants %}
|
||||
|
||||
/**
|
||||
* @brief {{ pc.struct_name }} Push Constant
|
||||
* @details Size: {{ pc.size }} bytes
|
||||
*/
|
||||
struct {{ pc.struct_name }} {
|
||||
{% for member in pc.members %}
|
||||
{{ member.cpp_type }} {{ member.name }};
|
||||
{% endfor %}
|
||||
};
|
||||
static_assert(sizeof({{ pc.struct_name }}) <= 128, "Push constant exceeds 128 bytes");
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if stage_resources %}
|
||||
// ============================================================================
|
||||
// 按阶段划分的 Push Constant 结构体
|
||||
// ============================================================================
|
||||
{% for stage_key in ['VERT', 'FRAG', 'COMP', 'GEOM', 'TESC', 'TESE'] %}
|
||||
{% set key = stage_key.lower() + '_push_constants' %}
|
||||
{% set stage_prefix = stage_key %}
|
||||
{% set pcs = stage_resources.get(key, []) %}
|
||||
{% if pcs %}
|
||||
// -------------------------- {{ stage_prefix }} 阶段 --------------------------
|
||||
{% for pc in pcs %}
|
||||
/**
|
||||
* @brief {{ pc.name }} ({{ stage_prefix }})
|
||||
* @details Size: {{ pc.size }} bytes
|
||||
*/
|
||||
struct {{ pc.name }}_{{ stage_prefix }} {
|
||||
{% for member in pc.members %}
|
||||
{{ member.cpp_type }} {{ member.name }};
|
||||
{% endfor %}
|
||||
};
|
||||
static_assert(sizeof({{ pc.name }}_{{ stage_prefix }}) <= 128, "Push constant exceeds 128 bytes");
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
// ============================================================================
|
||||
// 绑定辅助类
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief {{ snake_name }} 着色器绑定辅助类
|
||||
*/
|
||||
class {{ snake_name }}_bindings {
|
||||
public:
|
||||
{% if bindings %}
|
||||
/// 描述符集和绑定常量
|
||||
{% for binding in bindings %}
|
||||
static constexpr uint32_t SET_{{ binding.const_name }} = {{ binding.set }};
|
||||
static constexpr uint32_t BINDING_{{ binding.const_name }} = {{ binding.binding }};
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
/// Uniform Buffer 数量
|
||||
static constexpr uint32_t UNIFORM_BUFFER_COUNT = {{ uniform_buffer_count }};
|
||||
|
||||
/// 采样器数量
|
||||
static constexpr uint32_t SAMPLER_COUNT = {{ sampler_count }};
|
||||
|
||||
{% if 'VERT' in stages %}
|
||||
/// 获取顶点着色器 SPIR-V
|
||||
[[nodiscard]] static std::span<const uint32_t> get_vertex_spirv() noexcept {
|
||||
return {{ upper_snake_name }}_VERT_SPIRV;
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
{% if 'FRAG' in stages %}
|
||||
/// 获取片段着色器 SPIR-V
|
||||
[[nodiscard]] static std::span<const uint32_t> get_fragment_spirv() noexcept {
|
||||
return {{ upper_snake_name }}_FRAG_SPIRV;
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
{% if 'COMP' in stages %}
|
||||
/// 获取计算着色器 SPIR-V
|
||||
[[nodiscard]] static std::span<const uint32_t> get_compute_spirv() noexcept {
|
||||
return {{ upper_snake_name }}_COMP_SPIRV;
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
/// 获取着色器名称
|
||||
[[nodiscard]] static constexpr std::string_view get_name() noexcept {
|
||||
return "{{ shader_name }}";
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 静态反射元数据(使用 shader_types.hpp 中定义的结构)
|
||||
// ============================================================================
|
||||
|
||||
{% if uniform_buffers %}
|
||||
{% for ub in uniform_buffers %}
|
||||
{% if ub.members %}
|
||||
/// {{ ub.name }} Uniform Block 成员元数据
|
||||
{% set members_array = upper_snake_name + '_' + ub.snake_name | upper + '_MEMBERS' %}
|
||||
inline constexpr std::array<static_uniform_member, {{ ub.members | length }}> {{ members_array }} = {
|
||||
{% for member in ub.members %}
|
||||
static_uniform_member{"{{ member.name }}", "{{ member.type_name }}", {{ member.offset }}, {{ member.size }}}{{ "," if not loop.last else "" }}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
/// Uniform Blocks 元数据
|
||||
{% set ub_array_name = upper_snake_name + '_UNIFORM_BLOCKS' %}
|
||||
inline constexpr std::array<static_uniform_block_info, {{ uniform_buffers | length }}> {{ ub_array_name }} = {
|
||||
{% for ub in uniform_buffers %}
|
||||
static_uniform_block_info{
|
||||
"{{ ub.name }}",
|
||||
{{ ub.set }},
|
||||
{{ ub.binding }},
|
||||
{{ ub.size }},
|
||||
{% if ub.members %}
|
||||
{{ upper_snake_name }}_{{ ub.snake_name | upper }}_MEMBERS
|
||||
{% else %}
|
||||
{}
|
||||
{% endif %}
|
||||
}{{ "," if not loop.last else "" }}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% endif %}
|
||||
{% if samplers %}
|
||||
/// 纹理/采样器元数据
|
||||
{% set textures_array_name = upper_snake_name + '_TEXTURES' %}
|
||||
inline constexpr std::array<static_texture_info, {{ samplers | length }}> {{ textures_array_name }} = {
|
||||
{% for s in samplers %}
|
||||
static_texture_info{"{{ s.name }}", {{ s.set }}, {{ s.binding }}, "{{ s.dimension }}"}{{ "," if not loop.last else "" }}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% endif %}
|
||||
{% if push_constants %}
|
||||
{% for pc in push_constants %}
|
||||
{% if pc.members %}
|
||||
{% set members_array = upper_snake_name + '_' + pc.snake_name | upper + '_PC_MEMBERS' %}
|
||||
/// {{ pc.name }} Push Constant 成员元数据
|
||||
inline constexpr std::array<static_push_constant_member, {{ pc.members | length }}> {{ members_array }} = {
|
||||
{% for member in pc.members %}
|
||||
static_push_constant_member{"{{ member.name }}", "{{ member.type_name }}", {{ member.offset }}, {{ member.size }}}{{ "," if not loop.last else "" }}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
/// Push Constants 元数据
|
||||
{% set pc_array_name = upper_snake_name + '_PUSH_CONSTANTS' %}
|
||||
inline constexpr std::array<static_push_constant_info, {{ push_constants | length }}> {{ pc_array_name }} = {
|
||||
{% for pc in push_constants %}
|
||||
static_push_constant_info{
|
||||
"{{ pc.name }}",
|
||||
{{ pc.stage_flags }},
|
||||
{{ pc.offset }},
|
||||
{{ pc.size }},
|
||||
{% if pc.members %}
|
||||
{{ upper_snake_name }}_{{ pc.snake_name | upper }}_PC_MEMBERS
|
||||
{% else %}
|
||||
{}
|
||||
{% endif %}
|
||||
}{{ "," if not loop.last else "" }}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% endif %}
|
||||
{% if vertex_inputs %}
|
||||
/// 顶点输入属性元数据
|
||||
{% set vi_array_name = upper_snake_name + '_VERTEX_INPUTS' %}
|
||||
inline constexpr std::array<static_vertex_input_info, {{ vertex_inputs | length }}> {{ vi_array_name }} = {
|
||||
{% for vi in vertex_inputs %}
|
||||
static_vertex_input_info{"{{ vi.name }}", {{ vi.location }}, "{{ vi.type_name }}", {{ vi.format }}, {{ vi.offset }}}{{ "," if not loop.last else "" }}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% endif %}
|
||||
/// 完整的静态着色器反射数据
|
||||
inline constexpr static_shader_reflection {{ upper_snake_name }}_REFLECTION = {
|
||||
"{{ shader_name }}",
|
||||
{% if uniform_buffers %}
|
||||
{{ upper_snake_name }}_UNIFORM_BLOCKS,
|
||||
{% else %}
|
||||
{},
|
||||
{% endif %}
|
||||
{% if samplers %}
|
||||
{{ upper_snake_name }}_TEXTURES,
|
||||
{% else %}
|
||||
{},
|
||||
{% endif %}
|
||||
{% if push_constants %}
|
||||
{{ upper_snake_name }}_PUSH_CONSTANTS,
|
||||
{% else %}
|
||||
{},
|
||||
{% endif %}
|
||||
{% if vertex_inputs %}
|
||||
{{ upper_snake_name }}_VERTEX_INPUTS
|
||||
{% else %}
|
||||
{}
|
||||
{% endif %}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief {{ snake_name }} 着色器反射辅助类
|
||||
*/
|
||||
struct {{ snake_name }}_reflection {
|
||||
static constexpr std::string_view NAME = "{{ shader_name }}";
|
||||
|
||||
/// 获取完整的反射数据
|
||||
[[nodiscard]] static constexpr const static_shader_reflection& get() noexcept {
|
||||
return {{ upper_snake_name }}_REFLECTION;
|
||||
}
|
||||
|
||||
{% if uniform_buffers %}
|
||||
/// 获取 Uniform Blocks
|
||||
[[nodiscard]] static constexpr std::span<const static_uniform_block_info> uniform_blocks() noexcept {
|
||||
return {{ upper_snake_name }}_UNIFORM_BLOCKS;
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
{% if samplers %}
|
||||
/// 获取纹理/采样器
|
||||
[[nodiscard]] static constexpr std::span<const static_texture_info> textures() noexcept {
|
||||
return {{ upper_snake_name }}_TEXTURES;
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
{% if push_constants %}
|
||||
/// 获取 Push Constants
|
||||
[[nodiscard]] static constexpr std::span<const static_push_constant_info> push_constants() noexcept {
|
||||
return {{ upper_snake_name }}_PUSH_CONSTANTS;
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
{% if vertex_inputs %}
|
||||
/// 获取顶点输入属性
|
||||
[[nodiscard]] static constexpr std::span<const static_vertex_input_info> vertex_inputs() noexcept {
|
||||
return {{ upper_snake_name }}_VERTEX_INPUTS;
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
};
|
||||
|
||||
} // namespace mirai::generated
|
||||
377
tools/templates/widget_bindings.hpp.j2
Normal file
377
tools/templates/widget_bindings.hpp.j2
Normal file
@@ -0,0 +1,377 @@
|
||||
/**
|
||||
* @file {{ snake_name }}_bindings.hpp
|
||||
* @brief {{ shader_name }} 着色器绑定模板类 - 自动生成
|
||||
* @generated by generate_shader_bindings.py at {{ timestamp }}
|
||||
*
|
||||
* 本文件包含 {{ shader_name }} 着色器的所有绑定数据。
|
||||
* 控件使用此模板类时应静态检查是否满足资源需求。
|
||||
*
|
||||
* 使用方法:
|
||||
* 1. 继承 shader_widget 并指定此绑定类
|
||||
* 2. 静态检查所需资源是否存在
|
||||
* 3. 使用 bindings_type:: 访问绑定常量和数据
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
|
||||
{% if has_vertex_shader %}
|
||||
#include <vulkan/vulkan.hpp>
|
||||
{% endif %}
|
||||
|
||||
namespace mirai::generated {
|
||||
|
||||
// ============================================================================
|
||||
// {{ shader_name }} 着色器绑定模板类
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief {{ shader_name }} 绑定模板类
|
||||
*
|
||||
* 包含着色器的所有反射数据:
|
||||
* - Uniform Buffer 数据结构
|
||||
* - Push Constant 数据结构
|
||||
* - 绑定常量
|
||||
* - SPIR-V 字节码
|
||||
*
|
||||
* 控件使用此模板时应静态检查所需资源:
|
||||
* - 检查 has_samplers 是否满足纹理输入需求
|
||||
* - 检查 has_uniform_buffers 是否满足 Uniform Buffer 需求
|
||||
* - 检查 has_push_constants 是否满足推送常量需求
|
||||
*/
|
||||
struct {{ snake_name }}_bindings {
|
||||
// ========================================================================
|
||||
// 静态特征检查
|
||||
// ========================================================================
|
||||
|
||||
/// 是否有顶点着色器
|
||||
static constexpr bool HAS_VERTEX_SHADER = {{ 'true' if has_vertex_shader else 'false' }};
|
||||
|
||||
/// 是否有片段着色器
|
||||
static constexpr bool HAS_FRAGMENT_SHADER = {{ 'true' if has_fragment_shader else 'false' }};
|
||||
|
||||
/// 是否有 Uniform Buffers
|
||||
static constexpr bool HAS_UNIFORM_BUFFERS = {{ 'true' if has_uniform_buffers else 'false' }};
|
||||
|
||||
/// 是否有 Push Constants
|
||||
static constexpr bool HAS_PUSH_CONSTANTS = {{ 'true' if has_push_constants else 'false' }};
|
||||
|
||||
/// 是否有 Samplers (纹理输入)
|
||||
static constexpr bool HAS_SAMPLERS = {{ 'true' if has_samplers else 'false' }};
|
||||
|
||||
/// Uniform Buffer 数量
|
||||
static constexpr uint32_t UNIFORM_BUFFER_COUNT = {{ uniform_buffer_count }};
|
||||
|
||||
/// Sampler 数量
|
||||
static constexpr uint32_t SAMPLER_COUNT = {{ sampler_count }};
|
||||
|
||||
// ========================================================================
|
||||
// Uniform Buffer 结构定义
|
||||
// ========================================================================
|
||||
|
||||
{% if uniform_buffers %}
|
||||
{% for ub in uniform_buffers %}
|
||||
{% if ub.members %}
|
||||
/**
|
||||
* @brief {{ ub.name }} Uniform Buffer 数据结构
|
||||
*/
|
||||
struct {{ ub.struct_name }} {
|
||||
{% for member in ub.members %}
|
||||
/// {{ member.name }}
|
||||
{{ member.cpp_type }} {{ member.name }}{{ '[' + member.array_size|string + ']' if member.array_size > 1 else '' }};
|
||||
{% endfor %}
|
||||
};
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
// ========================================================================
|
||||
// Push Constant 结构定义
|
||||
// ========================================================================
|
||||
|
||||
{% if push_constants %}
|
||||
{% for pc in push_constants %}
|
||||
{% if pc.members %}
|
||||
/**
|
||||
* @brief {{ pc.name }} Push Constant 数据结构
|
||||
*/
|
||||
struct {{ pc.struct_name }} {
|
||||
{% for member in pc.members %}
|
||||
/// {{ member.name }}
|
||||
{{ member.cpp_type }} {{ member.name }};
|
||||
{% endfor %}
|
||||
};
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
// ========================================================================
|
||||
// 绑定常量 (按资源类型分组)
|
||||
// ========================================================================
|
||||
|
||||
{% if samplers %}
|
||||
// -------------------- Sampler 绑定常量 --------------------
|
||||
{% for s in samplers %}
|
||||
/// {{ s.name }} Set
|
||||
static constexpr uint32_t {{ s.name | upper }}_SET = {{ s.set }};
|
||||
/// {{ s.name }} Binding
|
||||
static constexpr uint32_t {{ s.name | upper }}_BINDING = {{ s.binding }};
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if uniform_buffers %}
|
||||
// -------------------- Uniform Buffer 绑定常量 --------------------
|
||||
{% for ub in uniform_buffers %}
|
||||
/// {{ ub.name }} Set
|
||||
static constexpr uint32_t {{ ub.name | upper }}_SET = {{ ub.set }};
|
||||
/// {{ ub.name }} Binding
|
||||
static constexpr uint32_t {{ ub.name | upper }}_BINDING = {{ ub.binding }};
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
// ========================================================================
|
||||
// 数据成员
|
||||
// ========================================================================
|
||||
|
||||
{% if uniform_buffers %}
|
||||
{% for ub in uniform_buffers %}
|
||||
/// {{ ub.name }} 数据
|
||||
{{ ub.struct_name }} {{ ub.name | lower }}_data{};
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if push_constants %}
|
||||
{% for pc in push_constants %}
|
||||
{% if pc.members %}
|
||||
/// {{ pc.name }} 数据
|
||||
{{ pc.struct_name }} {{ pc.name | lower }}_data{};
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
// ========================================================================
|
||||
// SPIR-V 字节码访问方法
|
||||
// ========================================================================
|
||||
|
||||
{% if has_vertex_shader %}
|
||||
/**
|
||||
* @brief 获取顶点着色器 SPIR-V 字节码
|
||||
* @return 顶点着色器字节码视图
|
||||
*/
|
||||
[[nodiscard]] static constexpr std::span<const uint32_t> get_vertex_spirv() noexcept {
|
||||
{% if stages.VERT %}
|
||||
static constexpr std::array<uint32_t, {{ stages.VERT.word_count }}> VERT_SPIRV = {
|
||||
{% for line in stages.VERT.hex_lines %}
|
||||
{{ line }}
|
||||
{% endfor %}
|
||||
};
|
||||
return std::span<const uint32_t>(VERT_SPIRV);
|
||||
{% else %}
|
||||
return {};
|
||||
{% endif %}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if has_fragment_shader %}
|
||||
/**
|
||||
* @brief 获取片段着色器 SPIR-V 字节码
|
||||
* @return 片段着色器字节码视图
|
||||
*/
|
||||
[[nodiscard]] static constexpr std::span<const uint32_t> get_fragment_spirv() noexcept {
|
||||
{% if stages.FRAG %}
|
||||
static constexpr std::array<uint32_t, {{ stages.FRAG.word_count }}> FRAG_SPIRV = {
|
||||
{% for line in stages.FRAG.hex_lines %}
|
||||
{{ line }}
|
||||
{% endfor %}
|
||||
};
|
||||
return std::span<const uint32_t>(FRAG_SPIRV);
|
||||
{% else %}
|
||||
return {};
|
||||
{% endif %}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
// ========================================================================
|
||||
// 描述符集布局创建
|
||||
// ========================================================================
|
||||
|
||||
{% if samplers or uniform_buffers %}
|
||||
/**
|
||||
* @brief 创建描述符集布局
|
||||
* @param device Vulkan 设备
|
||||
* @return 描述符集布局
|
||||
*/
|
||||
[[nodiscard]] static vk::DescriptorSetLayout create_descriptor_set_layout(vk::Device& device) {
|
||||
std::vector<vk::DescriptorSetLayoutBinding> bindings;
|
||||
|
||||
{% if samplers %}
|
||||
{% for s in samplers %}
|
||||
// {{ s.name }} - Sampler
|
||||
bindings.push_back(vk::DescriptorSetLayoutBinding{}
|
||||
.setBinding({{ s.name | upper }}_BINDING)
|
||||
.setDescriptorType(vk::DescriptorType::eCombinedImageSampler)
|
||||
.setDescriptorCount(1)
|
||||
.setStageFlags(vk::ShaderStageFlagBits::eFragment));
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if uniform_buffers %}
|
||||
{% for ub in uniform_buffers %}
|
||||
// {{ ub.name }} - Uniform Buffer
|
||||
bindings.push_back(vk::DescriptorSetLayoutBinding{}
|
||||
.setBinding({{ ub.name | upper }}_BINDING)
|
||||
.setDescriptorType(vk::DescriptorType::eUniformBuffer)
|
||||
.setDescriptorCount(1)
|
||||
.setStageFlags(vk::ShaderStageFlagBits::eFragment));
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo layout_info{};
|
||||
layout_info.setBindings(bindings);
|
||||
|
||||
auto [result, layout] = device.createDescriptorSetLayout(layout_info);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
{% else %}
|
||||
/**
|
||||
* @brief 创建描述符集布局 (无资源时返回空布局)
|
||||
* @return VK_NULL_HANDLE
|
||||
*/
|
||||
[[nodiscard]] static vk::DescriptorSetLayout create_descriptor_set_layout(vk::Device&) noexcept {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
// ========================================================================
|
||||
// 描述符池创建
|
||||
// ========================================================================
|
||||
|
||||
{% if samplers or uniform_buffers %}
|
||||
/**
|
||||
* @brief 创建描述符池
|
||||
* @param device Vulkan 设备
|
||||
* @param max_sets 最大描述符集数量
|
||||
* @return 描述符池
|
||||
*/
|
||||
[[nodiscard]] static vk::DescriptorPool create_descriptor_pool(vk::Device& device, uint32_t max_sets = 1) {
|
||||
std::vector<vk::DescriptorPoolSize> pool_sizes;
|
||||
|
||||
{% if samplers %}
|
||||
pool_sizes.push_back(vk::DescriptorPoolSize{}
|
||||
.setType(vk::DescriptorType::eCombinedImageSampler)
|
||||
.setDescriptorCount({{ samplers | length }} * max_sets));
|
||||
{% endif %}
|
||||
|
||||
{% if uniform_buffers %}
|
||||
pool_sizes.push_back(vk::DescriptorPoolSize{}
|
||||
.setType(vk::DescriptorType::eUniformBuffer)
|
||||
.setDescriptorCount({{ uniform_buffers | length }} * max_sets));
|
||||
{% endif %}
|
||||
|
||||
vk::DescriptorPoolCreateInfo pool_info{};
|
||||
pool_info.setPoolSizes(pool_sizes);
|
||||
pool_info.setMaxSets(max_sets);
|
||||
|
||||
auto [result, pool] = device.createDescriptorPool(pool_info);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
{% else %}
|
||||
/**
|
||||
* @brief 创建描述符池 (无资源时返回空池)
|
||||
* @return VK_NULL_HANDLE
|
||||
*/
|
||||
[[nodiscard]] static vk::DescriptorPool create_descriptor_pool(vk::Device&, uint32_t = 1) noexcept {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
// ========================================================================
|
||||
// 推送常量范围获取
|
||||
// ========================================================================
|
||||
|
||||
{% if push_constants %}
|
||||
/**
|
||||
* @brief 获取推送常量范围列表
|
||||
* @return 推送常量范围列表
|
||||
*/
|
||||
[[nodiscard]] static std::vector<vk::PushConstantRange> get_push_constant_ranges() {
|
||||
std::vector<vk::PushConstantRange> ranges;
|
||||
{% for pc in push_constants %}
|
||||
ranges.push_back(vk::PushConstantRange{}
|
||||
.setStageFlags(vk::ShaderStageFlagBits::eFragment)
|
||||
.setOffset({{ pc.offset }})
|
||||
.setSize({{ pc.size }}));
|
||||
{% endfor %}
|
||||
return ranges;
|
||||
}
|
||||
{% else %}
|
||||
/**
|
||||
* @brief 获取推送常量范围列表 (无推送常量时返回空列表)
|
||||
* @return 空列表
|
||||
*/
|
||||
[[nodiscard]] static std::vector<vk::PushConstantRange> get_push_constant_ranges() {
|
||||
return {};
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
// ========================================================================
|
||||
// 静态检查辅助模板
|
||||
// ========================================================================
|
||||
|
||||
{% if samplers %}
|
||||
/**
|
||||
* @brief 检查是否包含指定名称的 Sampler
|
||||
*/
|
||||
template<uint32_t Binding>
|
||||
struct has_sampler_binding : std::false_type {};
|
||||
|
||||
{% for s in samplers %}
|
||||
/**
|
||||
* @brief {{ s.name }} Sampler 绑定检查
|
||||
*/
|
||||
template<>
|
||||
struct has_sampler_binding<{{ s.name | upper }}_BINDING> : std::true_type {};
|
||||
{% endfor %}
|
||||
|
||||
/**
|
||||
* @brief 获取指定绑定的 Sampler 名称
|
||||
*/
|
||||
template<uint32_t Binding>
|
||||
static constexpr const char* get_sampler_name() {
|
||||
{% for s in samplers %}
|
||||
if constexpr (Binding == {{ s.name | upper }}_BINDING) {
|
||||
return "{{ s.name }}";
|
||||
}
|
||||
{% endfor %}
|
||||
return nullptr;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if uniform_buffers %}
|
||||
/**
|
||||
* @brief 检查是否包含指定名称的 Uniform Buffer
|
||||
*/
|
||||
template<uint32_t Binding>
|
||||
struct has_uniform_buffer_binding : std::false_type {};
|
||||
|
||||
{% for ub in uniform_buffers %}
|
||||
/**
|
||||
* @brief {{ ub.name }} Uniform Buffer 绑定检查
|
||||
*/
|
||||
template<>
|
||||
struct has_uniform_buffer_binding<{{ ub.name | upper }}_BINDING> : std::true_type {};
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
};
|
||||
|
||||
} // namespace mirai::generated
|
||||
@@ -12,6 +12,14 @@
|
||||
"name": "sdl3",
|
||||
"version>=": "3.2.28"
|
||||
},
|
||||
{
|
||||
"name": "spirv-cross",
|
||||
"version>=": "1.3.296.0"
|
||||
},
|
||||
{
|
||||
"name": "nlohmann-json",
|
||||
"version>=": "3.12.0#1"
|
||||
},
|
||||
{
|
||||
"name": "stb",
|
||||
"version>=": "2024-07-29#1"
|
||||
|
||||
Reference in New Issue
Block a user