init
This commit is contained in:
commit
b7936d2e1a
55
.clang-format
Normal file
55
.clang-format
Normal file
@ -0,0 +1,55 @@
|
||||
# Generated by CLion for STL
|
||||
BasedOnStyle: LLVM
|
||||
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignConsecutiveAssignments: Consecutive
|
||||
AlignConsecutiveMacros: Consecutive
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: AlignAfterOperator
|
||||
AlignTrailingComments:
|
||||
Kind: Never
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
ColumnLimit: 120
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<yvals(_core)?\.h>$'
|
||||
Priority: 1
|
||||
- Regex: '^<(Windows|userenv)\.h>$'
|
||||
Priority: 3
|
||||
SortPriority: 3
|
||||
- Regex: '^<WinIoCtl\.h>$'
|
||||
Priority: 3
|
||||
SortPriority: 4
|
||||
- Regex: '^<__.*\.hpp>$'
|
||||
Priority: 2
|
||||
- Regex: '\.hpp[>"]$'
|
||||
Priority: 5
|
||||
- Regex: '.*'
|
||||
Priority: 2
|
||||
IndentCaseBlocks: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: true
|
||||
InsertBraces: true
|
||||
InsertNewlineAtEOF: true
|
||||
MaxEmptyLinesToKeep: 2
|
||||
NamespaceIndentation: All
|
||||
PointerAlignment: Left
|
||||
RemoveSemicolon: true
|
||||
SpaceAfterCStyleCast: true
|
||||
SpaceBeforeParens: Custom
|
||||
SpaceBeforeParensOptions:
|
||||
AfterRequiresInClause: true
|
||||
StatementMacros:
|
||||
- _EXTERN_CXX_WORKAROUND
|
||||
- _END_EXTERN_CXX_WORKAROUND
|
||||
- _STD_BEGIN
|
||||
- _STD_END
|
||||
- _STDEXT_BEGIN
|
||||
- _STDEXT_END
|
||||
- _FMT_P2286_BEGIN
|
||||
- _FMT_P2286_END
|
||||
- _EXTERN_C_UNLESS_PURE
|
||||
- _END_EXTERN_C_UNLESS_PURE
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/cmake-build-debug
|
||||
/cmake-build-release
|
||||
/.idea
|
||||
/scripts/shader_paths.txt
|
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
[submodule "third_party/msdfgen"]
|
||||
path = third_party/msdfgen
|
||||
url = https://github.com/Chlumsky/msdfgen.git
|
||||
[submodule "third_party/LLGL"]
|
||||
path = third_party/LLGL
|
||||
url = https://github.com/LukasBanana/LLGL.git
|
37
CMakeLists.txt
Normal file
37
CMakeLists.txt
Normal file
@ -0,0 +1,37 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
cmake_policy(SET CMP0167 NEW)
|
||||
|
||||
project(mirage LANGUAGES C CXX)
|
||||
set(CMAKE_CXX_STANDARD 26)
|
||||
|
||||
# 设置全局着色器输出目录
|
||||
set(SHADER_OUTPUT_DIR "${CMAKE_BINARY_DIR}/shaders" CACHE PATH "着色器编译输出路径")
|
||||
# 设置全局着色器路径列表
|
||||
set(SHADER_SOURCE_DIRS "" CACHE STRING "着色器源文件路径列表")
|
||||
mark_as_advanced(SHADER_OUTPUT_DIR)
|
||||
|
||||
set(MSDFGEN_USE_SKIA OFF CACHE BOOL "Use Skia for MSDFGen" FORCE)
|
||||
set(MSDFGEN_USE_VCPKG OFF CACHE BOOL "Use VCPKG for MSDFGen" FORCE)
|
||||
set(MSDFGEN_USE_OPENMP ON CACHE BOOL "Use OpenMP for MSDFGen" FORCE)
|
||||
|
||||
include(cmake/retrieve_files.cmake)
|
||||
include(cmake/detect_os.cmake)
|
||||
include(cmake/configure_glfw_native.cmake)
|
||||
include(cmake/compile_shaders.cmake)
|
||||
include(cmake/config_macos.cmake)
|
||||
|
||||
# 如果是Debug模式, 添加宏定义
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
add_definitions(-DDEBUG=1)
|
||||
else ()
|
||||
add_definitions(-DDEBUG=0)
|
||||
endif ()
|
||||
|
||||
add_subdirectory(third_party/LLGL)
|
||||
add_subdirectory(third_party/msdfgen)
|
||||
add_subdirectory(src/core)
|
||||
|
||||
set(BUILD_EXAMPLE FALSE CACHE BOOL "Build example")
|
||||
if (BUILD_EXAMPLE)
|
||||
add_subdirectory(example)
|
||||
endif ()
|
49
cmake/compile_shaders.cmake
Normal file
49
cmake/compile_shaders.cmake
Normal file
@ -0,0 +1,49 @@
|
||||
# 查找编译器和Python
|
||||
find_program(PYTHON_EXECUTABLE python3)
|
||||
if (NOT PYTHON_EXECUTABLE)
|
||||
find_program(PYTHON_EXECUTABLE python)
|
||||
endif ()
|
||||
if (NOT PYTHON_EXECUTABLE)
|
||||
message(FATAL_ERROR "Python executable not found")
|
||||
endif ()
|
||||
find_program(SLANG_COMPILER slangc REQUIRED)
|
||||
|
||||
message(STATUS "Python executable: ${PYTHON_EXECUTABLE}")
|
||||
message(STATUS "SLANG compiler: ${SLANG_COMPILER}")
|
||||
|
||||
set(SHADER_PATH_FILE ${CMAKE_CURRENT_SOURCE_DIR}/scripts/shader_paths.txt)
|
||||
# 删除文件
|
||||
file(REMOVE ${SHADER_PATH_FILE})
|
||||
|
||||
function(shader_compile_target INPUT_DIR)
|
||||
# 将路径写到scripts/shader_paths.txt中
|
||||
file(WRITE ${SHADER_PATH_FILE} ${INPUT_DIR})
|
||||
endfunction()
|
||||
|
||||
set(SLANGC_ARGS "")
|
||||
if (LLGL_BUILD_RENDERER_OPENGL OR LLGL_BUILD_RENDERER_OPENGLES3 OR LLGL_BUILD_RENDERER_WEBGL)
|
||||
list(APPEND SLANGC_ARGS "--opengl")
|
||||
endif ()
|
||||
if (LLGL_BUILD_RENDERER_VULKAN)
|
||||
list(APPEND SLANGC_ARGS "--vulkan")
|
||||
endif ()
|
||||
if (LLGL_BUILD_RENDERER_DIRECT3D11)
|
||||
list(APPEND SLANGC_ARGS "--d3d11")
|
||||
endif ()
|
||||
if (LLGL_BUILD_RENDERER_DIRECT3D12)
|
||||
list(APPEND SLANGC_ARGS "--d3d12")
|
||||
endif ()
|
||||
if (LLGL_BUILD_RENDERER_METAL)
|
||||
list(APPEND SLANGC_ARGS "--metal")
|
||||
endif ()
|
||||
# 如果是Debug模式, 添加--debug选项
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
list(APPEND SLANGC_ARGS "--debug")
|
||||
endif ()
|
||||
|
||||
# 添加自定义命令, 用于编译着色器, 调用scripts/compile_shaders.py
|
||||
add_custom_target(compile_shaders
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/compile_shaders.py --shader-list ${SHADER_PATH_FILE} --output-dir ${SHADER_OUTPUT_DIR} ${SLANGC_ARGS}
|
||||
COMMENT "Compiling shaders"
|
||||
VERBATIM
|
||||
)
|
16
cmake/config_macos.cmake
Normal file
16
cmake/config_macos.cmake
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
# 如果是Macos
|
||||
if (APPLE)
|
||||
# 获取 Homebrew 安装的 libomp 路径
|
||||
execute_process(
|
||||
COMMAND brew --prefix libomp
|
||||
OUTPUT_VARIABLE HOMEBREW_LIBOMP_PATH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
# 设置 OpenMP 路径变量
|
||||
set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I${HOMEBREW_LIBOMP_PATH}/include")
|
||||
set(OpenMP_CXX_LIB_NAMES "omp")
|
||||
set(OpenMP_omp_LIBRARY "${HOMEBREW_LIBOMP_PATH}/lib/libomp.dylib")
|
||||
enable_language(OBJC OBJCXX)
|
||||
endif ()
|
39
cmake/configure_glfw_native.cmake
Normal file
39
cmake/configure_glfw_native.cmake
Normal file
@ -0,0 +1,39 @@
|
||||
function(configure_glfw_native target)
|
||||
# 检测操作系统
|
||||
if(WIN32)
|
||||
target_compile_definitions(${target} PRIVATE GLFW_EXPOSE_NATIVE_WIN32)
|
||||
message(STATUS "Exposing GLFW native Win32 API")
|
||||
elseif(APPLE)
|
||||
target_compile_definitions(${target} PRIVATE GLFW_EXPOSE_NATIVE_COCOA)
|
||||
message(STATUS "Exposing GLFW native Cocoa API")
|
||||
elseif(UNIX)
|
||||
# 对于 Unix-like 系统,我们需要进一步检测
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
# 检测 Wayland
|
||||
find_package(Wayland)
|
||||
if(Wayland_FOUND)
|
||||
target_compile_definitions(${target} PRIVATE GLFW_EXPOSE_NATIVE_WAYLAND)
|
||||
message(STATUS "Exposing GLFW native Wayland API")
|
||||
else()
|
||||
# 如果没有 Wayland,默认使用 X11
|
||||
target_compile_definitions(${target} PRIVATE GLFW_EXPOSE_NATIVE_X11)
|
||||
message(STATUS "Exposing GLFW native X11 API")
|
||||
endif()
|
||||
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD|OpenBSD|NetBSD")
|
||||
# BSD 系统通常使用 X11
|
||||
target_compile_definitions(${target} PRIVATE GLFW_EXPOSE_NATIVE_X11)
|
||||
message(STATUS "Exposing GLFW native X11 API for BSD")
|
||||
else()
|
||||
message(WARNING "Unknown Unix-like system, GLFW native API might not be properly exposed")
|
||||
endif()
|
||||
else()
|
||||
message(WARNING "Unknown operating system, GLFW native API might not be properly exposed")
|
||||
endif()
|
||||
|
||||
# 对于 EGL 支持,你可能需要额外的检测
|
||||
# 这里我们简单地为所有非 Windows 和非 macOS 系统启用它
|
||||
if(NOT WIN32 AND NOT APPLE)
|
||||
target_compile_definitions(${target} PRIVATE GLFW_EXPOSE_NATIVE_EGL)
|
||||
message(STATUS "Exposing GLFW native EGL API")
|
||||
endif()
|
||||
endfunction()
|
73
cmake/detect_os.cmake
Normal file
73
cmake/detect_os.cmake
Normal file
@ -0,0 +1,73 @@
|
||||
# DetectOS.cmake
|
||||
|
||||
function(add_os_definitions target)
|
||||
# 初始化所有平台宏为 0
|
||||
set(PLATFORMS MIRAGE_PLATFORM_WINDOWS MIRAGE_PLATFORM_MACOS MIRAGE_PLATFORM_LINUX MIRAGE_PLATFORM_FREEBSD MIRAGE_PLATFORM_IOS MIRAGE_PLATFORM_ANDROID)
|
||||
|
||||
# 检测操作系统并设置相应的宏为 1
|
||||
if(WIN32)
|
||||
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_WINDOWS=1)
|
||||
message(STATUS "Detected Windows operating system")
|
||||
list(REMOVE_ITEM PLATFORMS MIRAGE_PLATFORM_WINDOWS)
|
||||
elseif(APPLE AND UNIX)
|
||||
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_MACOS=1)
|
||||
message(STATUS "Detected macOS operating system")
|
||||
list(REMOVE_ITEM PLATFORMS MIRAGE_PLATFORM_MACOS)
|
||||
elseif(UNIX)
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_LINUX=1)
|
||||
message(STATUS "Detected Linux operating system")
|
||||
list(REMOVE_ITEM PLATFORMS MIRAGE_PLATFORM_LINUX)
|
||||
elseif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
|
||||
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_FREEBSD=1)
|
||||
message(STATUS "Detected FreeBSD operating system")
|
||||
list(REMOVE_ITEM PLATFORMS MIRAGE_PLATFORM_FREEBSD)
|
||||
else()
|
||||
message(WARNING "Detected unknown Unix-like operating system")
|
||||
endif()
|
||||
elseif(ANDROID)
|
||||
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_ANDROID=1)
|
||||
message(STATUS "Detected Android operating system")
|
||||
list(REMOVE_ITEM PLATFORMS MIRAGE_PLATFORM_ANDROID)
|
||||
elseif(IOS)
|
||||
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_IOS=1)
|
||||
message(STATUS "Detected iOS operating system")
|
||||
list(REMOVE_ITEM PLATFORMS MIRAGE_PLATFORM_IOS)
|
||||
else()
|
||||
message(WARNING "Unknown operating system")
|
||||
endif()
|
||||
|
||||
foreach(PLATFORM ${PLATFORMS})
|
||||
target_compile_definitions(${target} PUBLIC ${PLATFORM}=0)
|
||||
endforeach()
|
||||
|
||||
# 检测并设置架构宏
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_ARCH_64BIT=1 MIRAGE_PLATFORM_ARCH_32BIT=0)
|
||||
message(STATUS "Detected 64-bit architecture")
|
||||
else()
|
||||
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_ARCH_64BIT=0 MIRAGE_PLATFORM_ARCH_32BIT=1)
|
||||
message(STATUS "Detected 32-bit architecture")
|
||||
endif()
|
||||
|
||||
# 设置通用的 UNIX 宏
|
||||
if(UNIX)
|
||||
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_UNIX=1)
|
||||
else()
|
||||
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_UNIX=0)
|
||||
endif()
|
||||
|
||||
# 设置通用的 POSIX 宏
|
||||
if(UNIX OR APPLE)
|
||||
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_POSIX=1)
|
||||
else()
|
||||
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_POSIX=0)
|
||||
endif()
|
||||
|
||||
# 设置IS_MOBILE宏
|
||||
if(ANDROID OR IOS)
|
||||
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_IS_MOBILE=1)
|
||||
else()
|
||||
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_IS_MOBILE=0)
|
||||
endif()
|
||||
endfunction()
|
65
cmake/retrieve_files.cmake
Normal file
65
cmake/retrieve_files.cmake
Normal file
@ -0,0 +1,65 @@
|
||||
|
||||
function(retrieve_files_custom path extension out_files)
|
||||
message(STATUS "Retrieving files in ${path}")
|
||||
set(EXTENSIONS "")
|
||||
foreach(ext ${extension})
|
||||
list(APPEND EXTENSIONS "${path}/*.${ext}")
|
||||
endforeach ()
|
||||
|
||||
# 递归查找文件夹下的 .h .hpp. ini 文件保存到 HEAD_FILES
|
||||
file(GLOB_RECURSE FIND_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS ${EXTENSIONS})
|
||||
# 将 HEDADER_FILES 和 SRC_FILES 保存到 ALL_FILES 变量
|
||||
set(ALL_FILES ${FIND_FILES})
|
||||
|
||||
set(RESULT "")
|
||||
|
||||
# 对 ALL_FILES 变量里面的所有文件分类(保留资源管理器的目录结构)
|
||||
foreach(fileItem ${ALL_FILES})
|
||||
# Get the directory of the source file
|
||||
get_filename_component(PARENT_DIR "${fileItem}" DIRECTORY)
|
||||
|
||||
# 用于检查平台的条件
|
||||
if(PARENT_DIR STREQUAL "windows")
|
||||
if(WIN32)
|
||||
set(RESULT "${RESULT};${fileItem}")
|
||||
else()
|
||||
continue()
|
||||
endif()
|
||||
elseif(PARENT_DIR STREQUAL "linux")
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(RESULT "${RESULT};${fileItem}")
|
||||
else()
|
||||
continue()
|
||||
endif()
|
||||
elseif(PARENT_DIR STREQUAL "mac")
|
||||
if(APPLE)
|
||||
set(RESULT "${RESULT};${fileItem}")
|
||||
else()
|
||||
continue()
|
||||
endif()
|
||||
else()
|
||||
# 如果文件夹名称不是平台,则始终包含
|
||||
set(RESULT "${RESULT};${fileItem}")
|
||||
endif()
|
||||
|
||||
# Remove common directory prefix to make the group
|
||||
string(REPLACE "${path}" "" GROUP "${PARENT_DIR}")
|
||||
# Make sure we are using windows slashes
|
||||
string(REPLACE "/" "\\" GROUP "${GROUP}")
|
||||
# Group into "Source Files" and "Header Files"
|
||||
set(GROUP "${GROUP}")
|
||||
source_group("${GROUP}" FILES "${fileItem}")
|
||||
endforeach()
|
||||
|
||||
set(${out_files} ${RESULT} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(retrieve_files path out_files)
|
||||
set(temp_files "")
|
||||
if (APPLE)
|
||||
retrieve_files_custom(${path} "h;hpp;ini;cpp;c;ixx;mm" temp_files)
|
||||
else ()
|
||||
retrieve_files_custom(${path} "h;hpp;ini;cpp;c;ixx" temp_files)
|
||||
endif ()
|
||||
set(${out_files} ${${out_files}} ${temp_files} PARENT_SCOPE)
|
||||
endfunction()
|
7
example/CMakeLists.txt
Normal file
7
example/CMakeLists.txt
Normal file
@ -0,0 +1,7 @@
|
||||
project(mirage_example)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
|
||||
set(SRC_FILES "")
|
||||
retrieve_files(src SRC_FILES)
|
||||
add_executable(${PROJECT_NAME} ${SRC_FILES})
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE mirage_core)
|
12
example/src/main.cpp
Normal file
12
example/src/main.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include "mirage.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
window_desc desc{};
|
||||
desc.title = "mirage";
|
||||
desc.resolution.width = 1280;
|
||||
desc.resolution.height = 720;
|
||||
desc.resizable = true;
|
||||
|
||||
mirage::init_info init{};
|
||||
return run(init);
|
||||
}
|
135
scripts/compile_shaders.py
Normal file
135
scripts/compile_shaders.py
Normal file
@ -0,0 +1,135 @@
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import re
|
||||
|
||||
def find_shader_files(input_dir, extensions):
|
||||
for root, _, files in os.walk(input_dir):
|
||||
for file in files:
|
||||
if any(file.endswith(ext) for ext in extensions):
|
||||
yield os.path.join(root, file)
|
||||
|
||||
def find_slang_entries(input_file):
|
||||
try:
|
||||
with open(input_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
pattern = r'\[shader\(\s*"(?:\w+)"\s*\)\]\s*\n\s*\w+\s+(\w+)\s*\('
|
||||
return list(set(re.findall(pattern, content)))
|
||||
except Exception as e:
|
||||
print(f"Error parsing {input_file}: {str(e)}")
|
||||
return []
|
||||
|
||||
def gen_shader_ext(build_type):
|
||||
if build_type == 'glsl':
|
||||
return 'glsl'
|
||||
elif build_type == 'spirv':
|
||||
return 'spirv'
|
||||
elif build_type == 'dxil':
|
||||
return 'dxil'
|
||||
elif build_type == 'dxbc':
|
||||
return 'dxbc'
|
||||
elif build_type == 'metallib':
|
||||
return 'metallib'
|
||||
elif build_type == 'wgsl':
|
||||
return 'wgsl'
|
||||
else:
|
||||
return 'dat'
|
||||
|
||||
def gen_slangc_cmd(input_file, entry, output_file, target_type, args):
|
||||
slangc = args.slangc
|
||||
cmd = [slangc, input_file, "-entry", entry, "-o", output_file, "-target", target_type]
|
||||
if args.debug:
|
||||
cmd.append("-g3")
|
||||
else:
|
||||
cmd.append("-O3")
|
||||
|
||||
if target_type == 'glsl':
|
||||
cmd.extend(["-profile", "glsl_460"])
|
||||
if target_type == 'spirv':
|
||||
cmd.extend(["-profile", "glsl_460"])
|
||||
cmd.extend(["-capability", "glsl_spirv_1_6"])
|
||||
if target_type == 'dxbc':
|
||||
cmd.extend(["-profile", "sm_5_1"])
|
||||
if target_type == 'dxil':
|
||||
cmd.extend(["-profile", "sm_6_6"])
|
||||
if target_type == 'metallib':
|
||||
cmd.extend(["-capability", "metallib_3_1"])
|
||||
return cmd
|
||||
|
||||
def compile_slang(input_file, target_types, output_dir, args):
|
||||
try:
|
||||
entries = find_slang_entries(input_file)
|
||||
if not entries:
|
||||
print(f"Skipping {input_file}: No shader entries found")
|
||||
return True
|
||||
|
||||
base = os.path.splitext(os.path.basename(input_file))[0]
|
||||
|
||||
abs_path = os.path.abspath(output_dir)
|
||||
|
||||
os.makedirs(abs_path, exist_ok=True)
|
||||
|
||||
success = True
|
||||
|
||||
for target_type, enabled in target_types:
|
||||
if not enabled:
|
||||
continue
|
||||
for entry in entries:
|
||||
output_file = os.path.join(abs_path, f"{base}_{entry}.{gen_shader_ext(target_type)}")
|
||||
cmd = gen_slangc_cmd(input_file, entry, output_file, target_type, args)
|
||||
try:
|
||||
subprocess.run(cmd, check=True, capture_output=True, text=True)
|
||||
print(f"Compiled Slang: {input_file}:{entry} -> {output_file}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error compiling {input_file}:{entry}")
|
||||
print(e.stderr)
|
||||
success = False
|
||||
return success
|
||||
except Exception as e:
|
||||
print(f"Unexpected error with {input_file}: {str(e)}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Compile slang shaders using slangc")
|
||||
parser.add_argument("--shader-list", help="Input shader list .txt file")
|
||||
parser.add_argument("--output-dir", help="Output directory")
|
||||
parser.add_argument("--slangc", default="slangc", help="Path to slangc")
|
||||
parser.add_argument("--debug", action="store_true", help="Compile in debug mode")
|
||||
parser.add_argument("--opengl", action="store_true", help="Compile Slang for OpenGL")
|
||||
parser.add_argument("--vulkan", action="store_true", help="Compile Slang for Vulkan")
|
||||
parser.add_argument("--d3d11", action="store_true", help="Compile Slang for D3D11")
|
||||
parser.add_argument("--d3d12", action="store_true", help="Compile Slang for D3D12")
|
||||
parser.add_argument("--metal", action="store_true", help="Compile Slang for Metal")
|
||||
|
||||
args = parser.parse_args()
|
||||
target_types = [
|
||||
['glsl', args.opengl],
|
||||
['spirv', args.vulkan],
|
||||
['dxbc', args.d3d11],
|
||||
['dxil', args.d3d12],
|
||||
['metallib', args.metal],
|
||||
]
|
||||
|
||||
output_dir = args.output_dir or "shaders"
|
||||
shader_dir = args.shader_list or "shader_paths.txt"
|
||||
|
||||
# 读取当前同级目录下的shader_paths.txt
|
||||
with open(shader_dir, 'r') as f:
|
||||
shader_paths = f.readlines()
|
||||
|
||||
slang_ext = ['.slang']
|
||||
|
||||
all_success = True
|
||||
|
||||
for shader_path in shader_paths:
|
||||
# Compile Slang
|
||||
for file in find_shader_files(shader_path, slang_ext):
|
||||
if not compile_slang(file, target_types, output_dir, args):
|
||||
all_success = False
|
||||
|
||||
if not all_success:
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
44
src/core/CMakeLists.txt
Normal file
44
src/core/CMakeLists.txt
Normal file
@ -0,0 +1,44 @@
|
||||
project(mirage_core)
|
||||
set(CMAKE_CXX_STANDARD 26)
|
||||
|
||||
find_package(harfbuzz REQUIRED)
|
||||
find_package(Eigen3 REQUIRED)
|
||||
find_package(spdlog REQUIRED)
|
||||
find_package(Boost REQUIRED)
|
||||
find_package(Freetype REQUIRED)
|
||||
find_package(Vulkan REQUIRED)
|
||||
find_package(glfw3 REQUIRED)
|
||||
|
||||
set(RENDERER_SOURCES "")
|
||||
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR} RENDERER_SOURCES)
|
||||
|
||||
add_library(${PROJECT_NAME} STATIC ${RENDERER_SOURCES})
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC Freetype::Freetype glfw harfbuzz Eigen3::Eigen spdlog::spdlog msdfgen-full Boost::boost Vulkan::Vulkan)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE NOMINMAX)
|
||||
add_os_definitions(${PROJECT_NAME})
|
||||
configure_glfw_native(${PROJECT_NAME})
|
||||
if (APPLE)
|
||||
find_library(COCOA_LIBRARY Cocoa)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC ${COCOA_LIBRARY})
|
||||
endif ()
|
||||
|
||||
# 添加编译shader的自定义命令
|
||||
shader_compile_target(${CMAKE_CURRENT_SOURCE_DIR}/shaders)
|
||||
# 添加依赖, 当编译mirage_core时, 会先执行compile_shaders
|
||||
add_dependencies(${PROJECT_NAME} compile_shaders)
|
||||
|
||||
# 如果需要编译example, 添加自定义命令用于拷贝shader文件
|
||||
if (BUILD_EXAMPLE)
|
||||
# 复制${SHADER_OUTPUT_DIR}文件到${CMAKE_CURRENT_BINARY_DIR}/example/resource/shaders
|
||||
set(SOURCE_DIR ${SHADER_OUTPUT_DIR})
|
||||
set(DEST_DIR ${CMAKE_BINARY_DIR}/example/resource/shaders)
|
||||
|
||||
# 添加自定义命令, 当目标mirage_core被编译时, 将${SOURCE_DIR}文件复制到${DEST_DIR}
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${SOURCE_DIR} ${DEST_DIR}
|
||||
COMMENT "Copying shaders to example"
|
||||
)
|
||||
endif ()
|
1
src/core/async/async_task.cpp
Normal file
1
src/core/async/async_task.cpp
Normal file
@ -0,0 +1 @@
|
||||
#include "async_task.h"
|
5
src/core/async/async_task.h
Normal file
5
src/core/async/async_task.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
class async_task {
|
||||
|
||||
};
|
52
src/core/async/thread_event.h
Normal file
52
src/core/async/thread_event.h
Normal file
@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace mirage {
|
||||
class thread_event {
|
||||
public:
|
||||
thread_event() = default;
|
||||
explicit thread_event(bool manual_reset) : manual_reset(manual_reset) {}
|
||||
|
||||
void wait() {
|
||||
std::unique_lock lock(mutex);
|
||||
cv.wait(lock, [this] { return signaled; });
|
||||
if (!manual_reset) {
|
||||
signaled = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool wait(std::chrono::milliseconds timeout) {
|
||||
std::unique_lock lock(mutex);
|
||||
const bool was_signaled = cv.wait_for(lock, timeout, [this] { return signaled; });
|
||||
if (was_signaled && !manual_reset) {
|
||||
signaled = false;
|
||||
}
|
||||
return was_signaled;
|
||||
}
|
||||
|
||||
void signal() {
|
||||
std::lock_guard lock(mutex);
|
||||
signaled = true;
|
||||
if (manual_reset) {
|
||||
cv.notify_all();
|
||||
} else {
|
||||
cv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
// 重置事件为无信号状态(仅手动复位事件使用)
|
||||
void reset() {
|
||||
if (manual_reset) {
|
||||
std::lock_guard lock(mutex);
|
||||
signaled = false;
|
||||
}
|
||||
}
|
||||
private:
|
||||
std::condition_variable cv;
|
||||
std::mutex mutex;
|
||||
bool signaled = false;
|
||||
bool manual_reset = false;
|
||||
};
|
||||
}
|
48
src/core/async/thread_pool.cpp
Normal file
48
src/core/async/thread_pool.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#include "thread_pool.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace mirage {
|
||||
thread_pool::thread_pool(const size_t num_threads) : stop(false) {
|
||||
// 创建线程
|
||||
for (std::size_t i = 0; i < num_threads; ++i) {
|
||||
threads.emplace_back(&thread_pool::worker_thread, this);
|
||||
}
|
||||
}
|
||||
|
||||
thread_pool::~thread_pool() {
|
||||
stop = true;
|
||||
// 唤醒所有线程
|
||||
condition.notify_all();
|
||||
|
||||
for (auto& thread : threads) {
|
||||
if (thread.joinable()) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void thread_pool::worker_thread() {
|
||||
while (true) {
|
||||
std::function<void()> task;
|
||||
{
|
||||
std::unique_lock lock(queue_mutex);
|
||||
condition.wait(lock, [this] { return stop || !this->tasks.empty(); });
|
||||
if (stop && tasks.empty()) {
|
||||
return;
|
||||
}
|
||||
task = std::move(tasks.front());
|
||||
tasks.pop();
|
||||
}
|
||||
|
||||
try {
|
||||
task();
|
||||
} catch (const std::exception& e) {
|
||||
// 处理任务中的异常,避免线程池崩溃
|
||||
std::cerr << "Task exception: " << e.what() << std::endl;
|
||||
} catch (...) {
|
||||
std::cerr << "Task exception: unknown exception" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
182
src/core/async/thread_pool.h
Normal file
182
src/core/async/thread_pool.h
Normal file
@ -0,0 +1,182 @@
|
||||
#pragma once
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
|
||||
#include "thread_event.h"
|
||||
#include "containers/safe_queue.h"
|
||||
#include "containers/safe_vector.h"
|
||||
|
||||
namespace mirage {
|
||||
class thread_pool {
|
||||
public:
|
||||
/**
|
||||
* @brief 创建一个线程池
|
||||
* @param num_threads 线程池中线程的数量, 默认为 CPU 核心数减 1, 避免主线程被占用
|
||||
*/
|
||||
explicit thread_pool(size_t num_threads = std::thread::hardware_concurrency() - 1);
|
||||
~thread_pool();
|
||||
|
||||
/**
|
||||
* @brief 获取全局线程池
|
||||
* @return 全局线程池
|
||||
*/
|
||||
static thread_pool& global() {
|
||||
static thread_pool instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 提交一个任务到线程池
|
||||
* @tparam f 任务函数类型
|
||||
* @tparam args 任务函数参数类型
|
||||
* @param in_func 任务函数
|
||||
* @param in_args 任务函数参数
|
||||
* @return 任务的 std::future 对象
|
||||
* @code
|
||||
* auto res = thread_pool.submit([](int a, int b) { return a + b; }, 1, 2);
|
||||
* @endcode
|
||||
*/
|
||||
template<typename f, typename ...args>
|
||||
auto submit(f&& in_func, args&&... in_args) {
|
||||
using return_type = std::invoke_result_t<f, args...>;
|
||||
|
||||
if (stop) { throw std::runtime_error("submit on stopped ThreadPool"); }
|
||||
|
||||
auto task = std::make_shared<std::packaged_task<return_type()>>(
|
||||
std::bind(std::forward<f>(in_func), std::forward<args>(in_args)...)
|
||||
);
|
||||
|
||||
auto res = task->get_future();
|
||||
|
||||
{
|
||||
std::lock_guard lock(queue_mutex);
|
||||
tasks.emplace([task] { (*task)(); });
|
||||
}
|
||||
|
||||
condition.notify_one();
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename f, typename callback_f, typename ...args>
|
||||
auto submit_with_callback(f&& in_func, callback_f&& in_callback, args&&... in_args) {
|
||||
using return_type = std::invoke_result_t<f, args...>;
|
||||
|
||||
if (stop) { throw std::runtime_error("submit on stopped ThreadPool"); }
|
||||
|
||||
auto task = std::make_shared<std::packaged_task<return_type()>>(
|
||||
std::bind(std::forward<f>(in_func), std::forward<args>(in_args)...)
|
||||
);
|
||||
|
||||
auto res = task->get_future();
|
||||
|
||||
{
|
||||
std::lock_guard lock(queue_mutex);
|
||||
tasks.emplace([task, in_callback] {
|
||||
std::optional<return_type> callback_value;
|
||||
try {
|
||||
callback_value = (*task)();
|
||||
} catch (...) {
|
||||
callback_value = std::nullopt;
|
||||
}
|
||||
in_callback(callback_value);
|
||||
});
|
||||
}
|
||||
|
||||
condition.notify_one();
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 提交一个任务到线程池,并在主线程中执行回调函数
|
||||
* @tparam f 任务函数类型
|
||||
* @tparam callback_f 回调函数类型
|
||||
* @tparam args 任务函数参数类型
|
||||
* @param in_func 任务函数
|
||||
* @param in_callback 回调函数
|
||||
* @param in_args 任务函数参数
|
||||
* @return 任务的 std::future 对象
|
||||
* @code
|
||||
* auto func = [](int a, int b) { return a + b; };
|
||||
* auto callback = [](std::optional<int> result) { std::cout << result.value_or(-1) << std::endl; };
|
||||
* auto res = thread_pool.submit_with_main_thread_callback(func, callback, 1, 2);
|
||||
* @endcode
|
||||
*/
|
||||
template<typename f, typename callback_f, typename ...args>
|
||||
auto submit_with_main_thread_callback(f&& in_func, callback_f&& in_callback, args&&... in_args) {
|
||||
using return_type = std::invoke_result_t<f, args...>;
|
||||
|
||||
if (stop) { throw std::runtime_error("submit on stopped ThreadPool"); }
|
||||
|
||||
auto task = std::make_shared<std::packaged_task<return_type()>>(
|
||||
std::bind(std::forward<f>(in_func), std::forward<args>(in_args)...)
|
||||
);
|
||||
|
||||
auto res = task->get_future();
|
||||
|
||||
{
|
||||
auto task_with_callback = [this, task, callback = std::forward<callback_f>(in_callback), shared_res = res.share()] {
|
||||
std::optional<return_type> callback_value;
|
||||
try {
|
||||
(*task)();
|
||||
callback_value = shared_res.get();
|
||||
} catch (...) {
|
||||
// 如果出现异常,将异常信息传递给回调函数
|
||||
callback_value = std::nullopt;
|
||||
}
|
||||
// 创建一个包装后的回调任务,并将其添加到主线程回调队列
|
||||
std::lock_guard callback_lock(main_queue_mutex);
|
||||
main_thread_callbacks.emplace([callback, callback_value] {
|
||||
callback(callback_value);
|
||||
});
|
||||
};
|
||||
|
||||
std::lock_guard lock(queue_mutex);
|
||||
tasks.emplace(task_with_callback);
|
||||
}
|
||||
condition.notify_one();
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理主线程回调队列中的所有回调,这个方法应该在主线程中定期调用,以执行回调函数
|
||||
*/
|
||||
void process_main_thread_callbacks() {
|
||||
{
|
||||
std::lock_guard lock(main_queue_mutex);
|
||||
if (main_thread_callbacks.empty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::queue<std::function<void()>> callbacks;
|
||||
{
|
||||
std::lock_guard lock(main_queue_mutex);
|
||||
std::swap(callbacks, main_thread_callbacks);
|
||||
}
|
||||
|
||||
while (!callbacks.empty()) {
|
||||
auto &callback = callbacks.front();
|
||||
callback();
|
||||
callbacks.pop();
|
||||
}
|
||||
}
|
||||
private:
|
||||
void worker_thread();
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
std::queue<std::function<void()>> tasks;
|
||||
|
||||
// 同步原语
|
||||
std::mutex queue_mutex;
|
||||
std::condition_variable condition;
|
||||
std::atomic_bool stop;
|
||||
|
||||
// 主线程回调队列
|
||||
std::queue<std::function<void()>> main_thread_callbacks;
|
||||
std::mutex main_queue_mutex;
|
||||
};
|
||||
}
|
76
src/core/containers/lrucache.hpp
Normal file
76
src/core/containers/lrucache.hpp
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* File: lrucache.hpp
|
||||
* Author: Alexander Ponomarev
|
||||
*
|
||||
* Created on June 20, 2013, 5:09 PM
|
||||
*/
|
||||
|
||||
#ifndef _LRUCACHE_HPP_INCLUDED_
|
||||
#define _LRUCACHE_HPP_INCLUDED_
|
||||
|
||||
#include <unordered_map>
|
||||
#include <list>
|
||||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace cache {
|
||||
|
||||
template<typename key_t, typename value_t>
|
||||
class lru_cache {
|
||||
public:
|
||||
using key_value_pair_t = std::pair<key_t, value_t>;
|
||||
using list_iterator_t = typename std::list<key_value_pair_t>::iterator;
|
||||
|
||||
lru_cache(size_t max_size) :
|
||||
_max_size(max_size) {
|
||||
}
|
||||
|
||||
void put(const key_t& key, const value_t& value) {
|
||||
auto it = _cache_items_map.find(key);
|
||||
_cache_items_list.push_front(key_value_pair_t(key, value));
|
||||
if (it != _cache_items_map.end()) {
|
||||
_cache_items_list.erase(it->second);
|
||||
_cache_items_map.erase(it);
|
||||
}
|
||||
_cache_items_map[key] = _cache_items_list.begin();
|
||||
|
||||
if (_cache_items_map.size() > _max_size) {
|
||||
auto last = _cache_items_list.end();
|
||||
--last;
|
||||
_cache_items_map.erase(last->first);
|
||||
_cache_items_list.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
const value_t& get(const key_t& key) {
|
||||
auto it = _cache_items_map.find(key);
|
||||
if (it == _cache_items_map.end()) {
|
||||
throw std::range_error("There is no such key in cache");
|
||||
}
|
||||
_cache_items_list.splice(_cache_items_list.begin(), _cache_items_list, it->second);
|
||||
return it->second->second;
|
||||
}
|
||||
|
||||
value_t* find(const key_t& key) {
|
||||
auto it = _cache_items_map.find(key);
|
||||
return it == _cache_items_map.end() ? nullptr : &it->second->second;
|
||||
}
|
||||
|
||||
bool exists(const key_t& key) const {
|
||||
return _cache_items_map.contains(key);
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t size() const {
|
||||
return _cache_items_map.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::list<key_value_pair_t> _cache_items_list;
|
||||
std::unordered_map<key_t, list_iterator_t> _cache_items_map;
|
||||
size_t _max_size;
|
||||
};
|
||||
|
||||
} // namespace cache
|
||||
|
||||
#endif /* _LRUCACHE_HPP_INCLUDED_ */
|
||||
|
41
src/core/containers/safe_queue.h
Normal file
41
src/core/containers/safe_queue.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
template<typename T>
|
||||
class safe_queue {
|
||||
public:
|
||||
void push(const T& value) {
|
||||
std::lock_guard lock(mutex);
|
||||
queue.push(value);
|
||||
}
|
||||
|
||||
void push(T&& value) {
|
||||
std::lock_guard lock(mutex);
|
||||
queue.push(std::move(value));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<T> pop() {
|
||||
std::lock_guard lock(mutex);
|
||||
if (queue.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
T value = std::move(queue.front());
|
||||
queue.pop();
|
||||
return value;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto empty() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return queue.empty();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto size() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return queue.size();
|
||||
}
|
||||
private:
|
||||
mutable std::mutex mutex;
|
||||
std::queue<T> queue;
|
||||
};
|
96
src/core/containers/safe_vector.h
Normal file
96
src/core/containers/safe_vector.h
Normal file
@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
template<typename T>
|
||||
class safe_vector {
|
||||
public:
|
||||
void push_back(const T& value) {
|
||||
std::lock_guard lock(mutex);
|
||||
data.push_back(value);
|
||||
}
|
||||
|
||||
void push_back(T&& in_value) {
|
||||
std::lock_guard lock(mutex);
|
||||
data.push_back(std::move(in_value));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<T> pop_back(bool shrink = false) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (data.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
T value = std::move(data.back());
|
||||
data.pop_back();
|
||||
if (shrink) {
|
||||
data.shrink_to_fit();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<T> pop_front() {
|
||||
std::lock_guard lock(mutex);
|
||||
if (data.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
T value = std::move(data.front());
|
||||
data.erase(data.begin());
|
||||
return value;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto size() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return data.size();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto empty() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return data.empty();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto begin() {
|
||||
std::lock_guard lock(mutex);
|
||||
return data.begin();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto end() {
|
||||
std::lock_guard lock(mutex);
|
||||
return data.end();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto begin() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return data.begin();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto end() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return data.end();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto rbegin() {
|
||||
std::lock_guard lock(mutex);
|
||||
return data.rbegin();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto rend() {
|
||||
std::lock_guard lock(mutex);
|
||||
return data.rend();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto rbegin() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return data.rbegin();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto rend() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return data.rend();
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex;
|
||||
std::vector<T> data;
|
||||
};
|
60
src/core/mirage.cpp
Normal file
60
src/core/mirage.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include "async/thread_pool.h"
|
||||
#include "mirage.h"
|
||||
|
||||
using time_type = decltype(std::chrono::high_resolution_clock::now());
|
||||
std::chrono::duration<double> delta_time = {};
|
||||
time_type begin_time = {};
|
||||
time_type last_time = {};
|
||||
|
||||
namespace mirage {
|
||||
bool init(const init_info& in_info) {
|
||||
spdlog::info("初始化 mirage");
|
||||
begin_time = std::chrono::high_resolution_clock::now();
|
||||
last_time = std::chrono::high_resolution_clock::now();
|
||||
return true;
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
spdlog::info("mirage 销毁");
|
||||
}
|
||||
|
||||
void update() {
|
||||
thread_pool::global().process_main_thread_callbacks();
|
||||
|
||||
// 更新时间
|
||||
const auto& current_time = std::chrono::high_resolution_clock::now();
|
||||
delta_time = current_time - last_time;
|
||||
last_time = current_time;
|
||||
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
bool should_exit() {
|
||||
return false;
|
||||
}
|
||||
|
||||
int run(const init_info& in_init_info) {
|
||||
try {
|
||||
if (!init(in_init_info)) {
|
||||
return -1;
|
||||
}
|
||||
while (!should_exit()) {
|
||||
update();
|
||||
}
|
||||
destroy();
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("运行时异常: {}", e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
spdlog::error("未知异常");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::chrono::duration<double>& get_delta_time() { return delta_time; }
|
||||
|
||||
std::chrono::duration<double> get_total_time() {
|
||||
return std::chrono::high_resolution_clock::now() - begin_time;
|
||||
}
|
||||
}
|
15
src/core/mirage.h
Normal file
15
src/core/mirage.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#define MIRAGE_VERSION_MAJOR 0
|
||||
#define MIRAGE_VERSION_MINOR 1
|
||||
#define MIRAGE_VERSION_PATCH 0
|
||||
|
||||
namespace mirage {
|
||||
struct init_info {
|
||||
};
|
||||
int run(const init_info& in_init_info);
|
||||
|
||||
const std::chrono::duration<double>& get_delta_time();
|
||||
std::chrono::duration<double> get_total_time();
|
||||
}
|
7
src/core/misc/color.cpp
Normal file
7
src/core/misc/color.cpp
Normal file
@ -0,0 +1,7 @@
|
||||
#include "color.h"
|
||||
|
||||
namespace mirage {
|
||||
const linear_color linear_color::white = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
const linear_color linear_color::black = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
const linear_color linear_color::transparent = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
}
|
60
src/core/misc/color.h
Normal file
60
src/core/misc/color.h
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#include <complex>
|
||||
|
||||
namespace mirage {
|
||||
class linear_color {
|
||||
public:
|
||||
linear_color() : r(1), g(1), b(1), a(1) {
|
||||
}
|
||||
|
||||
linear_color(float in_r, float in_g, float in_b, float in_a = 1.0f) : r(in_r), g(in_g), b(in_b), a(in_a) {
|
||||
}
|
||||
|
||||
static linear_color from_srgb(float in_r, float in_g, float in_b, float in_a = 1.0f) {
|
||||
return linear_color(
|
||||
in_r <= 0.04045f ? in_r / 12.92f : std::pow((in_r + 0.055f) / 1.055f, 2.4f),
|
||||
in_g <= 0.04045f ? in_g / 12.92f : std::pow((in_g + 0.055f) / 1.055f, 2.4f),
|
||||
in_b <= 0.04045f ? in_b / 12.92f : std::pow((in_b + 0.055f) / 1.055f, 2.4f),
|
||||
in_a
|
||||
);
|
||||
}
|
||||
|
||||
static linear_color from_srgb(const linear_color& in_color) {
|
||||
return from_srgb(in_color.r, in_color.g, in_color.b, in_color.a);
|
||||
}
|
||||
|
||||
linear_color& operator+=(const linear_color& in_color) {
|
||||
r += in_color.r;
|
||||
g += in_color.g;
|
||||
b += in_color.b;
|
||||
a += in_color.a;
|
||||
return *this;
|
||||
}
|
||||
|
||||
linear_color& operator-=(const linear_color& in_color) {
|
||||
r -= in_color.r;
|
||||
g -= in_color.g;
|
||||
b -= in_color.b;
|
||||
a -= in_color.a;
|
||||
return *this;
|
||||
}
|
||||
|
||||
linear_color operator+(const linear_color& in_color) const {
|
||||
return { r + in_color.r, g + in_color.g, b + in_color.b, a + in_color.a };
|
||||
}
|
||||
|
||||
linear_color operator-(const linear_color& in_color) const {
|
||||
return { r - in_color.r, g - in_color.g, b - in_color.b, a - in_color.a };
|
||||
}
|
||||
|
||||
bool operator==(const linear_color& in_color) const {
|
||||
return r == in_color.r && g == in_color.g && b == in_color.b && a == in_color.a;
|
||||
}
|
||||
|
||||
float r, g, b, a;
|
||||
|
||||
static const linear_color white;
|
||||
static const linear_color black;
|
||||
static const linear_color transparent;
|
||||
};
|
||||
}
|
19
src/core/misc/intrusive_unset_optional_state.h
Normal file
19
src/core/misc/intrusive_unset_optional_state.h
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include <optional>
|
||||
|
||||
namespace mirage_private {
|
||||
template <typename optional_type, bool has_intrusive_unset_optional_state>
|
||||
struct optional_storage;
|
||||
}
|
||||
|
||||
namespace mirage {
|
||||
struct intrusive_unset_optional_state {
|
||||
template<typename>
|
||||
friend class std::optional;
|
||||
|
||||
template<typename, bool>
|
||||
friend struct mirage_private::optional_storage;
|
||||
private:
|
||||
explicit intrusive_unset_optional_state() = default;
|
||||
};
|
||||
}
|
65
src/core/misc/lazy_singleton.h
Normal file
65
src/core/misc/lazy_singleton.h
Normal file
@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
#include <stdexcept>
|
||||
|
||||
namespace mirage {
|
||||
class lazy_singleton_func {
|
||||
protected:
|
||||
template<class T> static void construct(void* in_place) { new (in_place) T(); }
|
||||
template<class T> static void destruct(T* in_instance) { in_instance->~T(); }
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class lazy_singleton : public lazy_singleton_func {
|
||||
public:
|
||||
static T& get() {
|
||||
return *get_lazy(construct<T>).ptr;
|
||||
}
|
||||
|
||||
static T* try_get() {
|
||||
return get_lazy(construct<T>).try_get_value();
|
||||
}
|
||||
|
||||
static void tear_down() {
|
||||
get_lazy(nullptr).reset();
|
||||
}
|
||||
|
||||
private:
|
||||
static lazy_singleton& get_lazy(void(*ctor)(void*)) {
|
||||
static lazy_singleton instance(ctor);
|
||||
return instance;
|
||||
}
|
||||
|
||||
explicit lazy_singleton(void(*ctor)(void*)) {
|
||||
if (ctor) {
|
||||
ctor(instance_data);
|
||||
}
|
||||
|
||||
ptr = ctor ? reinterpret_cast<T*>(instance_data) : nullptr;
|
||||
}
|
||||
|
||||
~lazy_singleton() {
|
||||
reset();
|
||||
}
|
||||
|
||||
T* try_get_value() {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
T& get_value() {
|
||||
if (!ptr) {
|
||||
throw std::runtime_error("lazy_singleton not initialized");
|
||||
}
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
if (ptr) {
|
||||
destruct(ptr);
|
||||
ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
alignas(T) unsigned char instance_data[sizeof(T)]{};
|
||||
T* ptr{};
|
||||
};
|
||||
}
|
179
src/core/misc/mapped_file.cpp
Normal file
179
src/core/misc/mapped_file.cpp
Normal file
@ -0,0 +1,179 @@
|
||||
#include "mapped_file.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace mirage {
|
||||
#ifdef _WIN32
|
||||
mapped_file_win::mapped_file_win() : data(nullptr), size(0), file_handle(INVALID_HANDLE_VALUE), mapping_handle(nullptr) {}
|
||||
|
||||
mapped_file_win::~mapped_file_win() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
mapped_file_win::mapped_file_win(mapped_file_win&& other) noexcept
|
||||
: data(other.data), size(other.size), file_handle(other.file_handle), mapping_handle(other.mapping_handle) {
|
||||
other.data = nullptr;
|
||||
other.size = 0;
|
||||
other.file_handle = INVALID_HANDLE_VALUE;
|
||||
other.mapping_handle = nullptr;
|
||||
}
|
||||
|
||||
mapped_file_win& mapped_file_win::operator=(mapped_file_win&& other) noexcept {
|
||||
if (this == &other)
|
||||
return *this;
|
||||
cleanup();
|
||||
data = other.data;
|
||||
size = other.size;
|
||||
file_handle = other.file_handle;
|
||||
mapping_handle = other.mapping_handle;
|
||||
|
||||
other.data = nullptr;
|
||||
other.size = 0;
|
||||
other.file_handle = INVALID_HANDLE_VALUE;
|
||||
other.mapping_handle = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool mapped_file_win::map_file(const std::wstring& filename) {
|
||||
cleanup();
|
||||
|
||||
file_handle = CreateFileW(
|
||||
filename.c_str(),
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr
|
||||
);
|
||||
|
||||
if (file_handle == INVALID_HANDLE_VALUE) {
|
||||
throw std::runtime_error("Failed to open file");
|
||||
}
|
||||
|
||||
LARGE_INTEGER file_size;
|
||||
if (!GetFileSizeEx(file_handle, &file_size)) {
|
||||
cleanup();
|
||||
throw std::runtime_error("Failed to get file size");
|
||||
}
|
||||
size = static_cast<size_t>(file_size.QuadPart);
|
||||
|
||||
mapping_handle = CreateFileMappingW(
|
||||
file_handle,
|
||||
nullptr,
|
||||
PAGE_READONLY,
|
||||
0,
|
||||
0,
|
||||
nullptr
|
||||
);
|
||||
|
||||
if (mapping_handle == nullptr) {
|
||||
cleanup();
|
||||
throw std::runtime_error("Failed to create file mapping");
|
||||
}
|
||||
|
||||
data = MapViewOfFile(
|
||||
mapping_handle,
|
||||
FILE_MAP_READ,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
||||
if (data == nullptr) {
|
||||
cleanup();
|
||||
throw std::runtime_error("Failed to map view of file");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void mapped_file_win::cleanup() {
|
||||
if (data) {
|
||||
UnmapViewOfFile(data);
|
||||
data = nullptr;
|
||||
}
|
||||
if (mapping_handle) {
|
||||
CloseHandle(mapping_handle);
|
||||
mapping_handle = nullptr;
|
||||
}
|
||||
if (file_handle != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(file_handle);
|
||||
file_handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
size = 0;
|
||||
}
|
||||
|
||||
void mapped_file_win::unmap() {
|
||||
cleanup();
|
||||
}
|
||||
#else
|
||||
mapped_file_unix::mapped_file_unix() : data(nullptr), size(0), fd(-1) {}
|
||||
|
||||
mapped_file_unix::~mapped_file_unix() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
mapped_file_unix::mapped_file_unix(mapped_file_unix&& other) noexcept
|
||||
: data(other.data), size(other.size), fd(other.fd) {
|
||||
other.data = nullptr;
|
||||
other.size = 0;
|
||||
other.fd = -1;
|
||||
}
|
||||
|
||||
mapped_file_unix& mapped_file_unix::operator=(mapped_file_unix&& other) noexcept {
|
||||
if (this != &other) {
|
||||
cleanup();
|
||||
data = other.data;
|
||||
size = other.size;
|
||||
fd = other.fd;
|
||||
|
||||
other.data = nullptr;
|
||||
other.size = 0;
|
||||
other.fd = -1;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool mapped_file_unix::map_file(const std::wstring& filename) {
|
||||
cleanup();
|
||||
|
||||
std::string utf8_filename(filename.begin(), filename.end());
|
||||
fd = open(utf8_filename.c_str(), O_RDONLY);
|
||||
if (fd == -1) {
|
||||
throw std::runtime_error("Failed to open file");
|
||||
}
|
||||
|
||||
struct stat sb;
|
||||
if (fstat(fd, &sb) == -1) {
|
||||
cleanup();
|
||||
throw std::runtime_error("Failed to get file size");
|
||||
}
|
||||
size = static_cast<size_t>(sb.st_size);
|
||||
|
||||
data = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (data == MAP_FAILED) {
|
||||
cleanup();
|
||||
throw std::runtime_error("Failed to map file");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void mapped_file_unix::cleanup() {
|
||||
if (data) {
|
||||
munmap(data, size);
|
||||
data = nullptr;
|
||||
}
|
||||
if (fd != -1) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
size = 0;
|
||||
}
|
||||
|
||||
void mapped_file_unix::unmap() {
|
||||
cleanup();
|
||||
}
|
||||
#endif
|
||||
}
|
95
src/core/misc/mapped_file.h
Normal file
95
src/core/misc/mapped_file.h
Normal file
@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstddef>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
|
||||
namespace mirage {
|
||||
class mapped_file_win {
|
||||
public:
|
||||
mapped_file_win();
|
||||
~mapped_file_win();
|
||||
|
||||
// 禁用拷贝
|
||||
mapped_file_win(const mapped_file_win&) = delete;
|
||||
mapped_file_win& operator=(const mapped_file_win&) = delete;
|
||||
|
||||
// 允许移动
|
||||
mapped_file_win(mapped_file_win&& other) noexcept;
|
||||
mapped_file_win& operator=(mapped_file_win&& other) noexcept;
|
||||
|
||||
bool map_file(const std::wstring& filename);
|
||||
void unmap();
|
||||
|
||||
[[nodiscard]] const void* get_data() const {
|
||||
return data;
|
||||
}
|
||||
[[nodiscard]] void* get_data() {
|
||||
return data;
|
||||
}
|
||||
[[nodiscard]] size_t get_size() const {
|
||||
return size;
|
||||
}
|
||||
[[nodiscard]] bool is_mapped() const {
|
||||
return data != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
void cleanup();
|
||||
|
||||
void* data;
|
||||
size_t size;
|
||||
HANDLE file_handle;
|
||||
HANDLE mapping_handle;
|
||||
};
|
||||
using mapped_file = mapped_file_win;
|
||||
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
class mapped_file_unix {
|
||||
public:
|
||||
mapped_file_unix();
|
||||
~mapped_file_unix();
|
||||
|
||||
// 禁用拷贝
|
||||
mapped_file_unix(const mapped_file_unix&) = delete;
|
||||
mapped_file_unix& operator=(const mapped_file_unix&) = delete;
|
||||
|
||||
// 允许移动
|
||||
mapped_file_unix(mapped_file_unix&& other) noexcept;
|
||||
mapped_file_unix& operator=(mapped_file_unix&& other) noexcept;
|
||||
|
||||
bool map_file(const std::wstring& filename);
|
||||
void unmap();
|
||||
|
||||
[[nodiscard]] const void* get_data() const {
|
||||
return data;
|
||||
}
|
||||
[[nodiscard]] void* get_data() {
|
||||
return data;
|
||||
}
|
||||
[[nodiscard]] size_t get_size() const {
|
||||
return size;
|
||||
}
|
||||
[[nodiscard]] bool is_mapped() const {
|
||||
return data != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
void cleanup();
|
||||
|
||||
void* data;
|
||||
size_t size;
|
||||
int fd;
|
||||
};
|
||||
using mapped_file = mapped_file_unix;
|
||||
#endif
|
||||
}
|
360
src/core/misc/pixel.h
Normal file
360
src/core/misc/pixel.h
Normal file
@ -0,0 +1,360 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Eigen/Eigen>
|
||||
|
||||
namespace mirage {
|
||||
template<typename From, typename To>
|
||||
To convert(const From& value) {
|
||||
// 如果源和目标类型相同,直接返回
|
||||
if constexpr (std::is_same_v<From, To>) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 获取 From 类型的最小值和最大值
|
||||
const auto source_min = static_cast<double>(std::numeric_limits<From>::lowest());
|
||||
const auto source_max = static_cast<double>(std::numeric_limits<From>::max());
|
||||
|
||||
// 获取 To 类型的最小值和最大值
|
||||
const auto target_min = static_cast<double>(std::numeric_limits<To>::lowest());
|
||||
const auto target_max = static_cast<double>(std::numeric_limits<To>::max());
|
||||
|
||||
// 线性映射公式
|
||||
auto normalized = (static_cast<double>(value) - source_min) / (source_max - source_min); // [0, 1]
|
||||
return static_cast<To>(target_min + normalized * (target_max - target_min));
|
||||
}
|
||||
|
||||
template<typename From, typename To>
|
||||
To color_convert(const From& value) {
|
||||
// 如果源和目标类型相同,直接返回
|
||||
if constexpr (std::is_same_v<From, To>) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 如果源和目标都是整数,使用整数到整数的转换
|
||||
if constexpr (std::is_integral_v<From> && std::is_integral_v<To>) {
|
||||
return convert<From, To>(value);
|
||||
}
|
||||
|
||||
// 如果是整数到浮点数的映射
|
||||
if constexpr (std::is_integral_v<From> && std::is_floating_point_v<To>) {
|
||||
const auto source_min = static_cast<double>(std::numeric_limits<From>::lowest()); // 整数最小值
|
||||
const auto source_max = static_cast<double>(std::numeric_limits<From>::max()); // 整数最大值
|
||||
|
||||
// 将整数值映射到 [0, 1] 区间
|
||||
double normalized = (static_cast<double>(value) - source_min) / (source_max - source_min);
|
||||
return static_cast<To>(normalized); // 映射到浮点数
|
||||
}
|
||||
|
||||
// 如果是浮点数到整数的映射
|
||||
if constexpr (std::is_floating_point_v<From> && std::is_integral_v<To>) {
|
||||
const auto target_min = static_cast<double>(std::numeric_limits<To>::lowest()); // 整数最小值
|
||||
const auto target_max = static_cast<double>(std::numeric_limits<To>::max()); // 整数最大值
|
||||
|
||||
// 将浮点数从 [0, 1] 的区间映射到目标整数范围
|
||||
double clamped = std::clamp(static_cast<double>(value), 0.0, 1.0); // 限制到 [0, 1]
|
||||
return static_cast<To>(target_min + clamped * (target_max - target_min));
|
||||
}
|
||||
|
||||
// 如果是浮点数到浮点数的映射
|
||||
if constexpr (std::is_floating_point_v<From> && std::is_floating_point_v<To>) {
|
||||
const auto source_min = static_cast<double>(std::numeric_limits<From>::lowest());
|
||||
const auto source_max = static_cast<double>(std::numeric_limits<From>::max());
|
||||
|
||||
const auto target_min = static_cast<double>(std::numeric_limits<To>::lowest());
|
||||
const auto target_max = static_cast<double>(std::numeric_limits<To>::max());
|
||||
|
||||
// 线性映射
|
||||
double normalized = (static_cast<double>(value) - source_min) / (source_max - source_min); // [0, 1]
|
||||
return static_cast<To>(target_min + normalized * (target_max - target_min));
|
||||
}
|
||||
|
||||
// 默认情况(应该不会触发)
|
||||
return static_cast<To>(value);
|
||||
}
|
||||
|
||||
template<typename T, int N>
|
||||
struct pixel {
|
||||
using pixel_type = T;
|
||||
|
||||
pixel() = default;
|
||||
pixel(const pixel& rhs) {
|
||||
for (int i = 0; i < N; i++) {
|
||||
data[i] = rhs.data[i];
|
||||
}
|
||||
}
|
||||
// 从不同类型的像素转换
|
||||
template<typename U, int N2>
|
||||
pixel(const pixel<U, N2>& rhs) {
|
||||
*this = rhs;
|
||||
}
|
||||
|
||||
T data[N];
|
||||
T& operator[](int i) { return data[i]; }
|
||||
|
||||
T& r() { return data[0]; }
|
||||
T& g() { return data[1]; }
|
||||
T& b() { return data[2]; }
|
||||
T& a() { return data[3]; }
|
||||
|
||||
T r() const { return data[0]; }
|
||||
T g() const { return data[1]; }
|
||||
T b() const { return data[2]; }
|
||||
T a() const { return data[3]; }
|
||||
|
||||
// 转换为亮度
|
||||
T luminance() const {
|
||||
return 0.2126f * data[0] + 0.7152f * data[1] + 0.0722f * data[2];
|
||||
}
|
||||
// 转换为灰度
|
||||
T grayscale() const {
|
||||
return (data[0] + data[1] + data[2]) / 3;
|
||||
}
|
||||
// 转换为sRGB
|
||||
T sRGB() const {
|
||||
return data[0] <= 0.0031308f ? 12.92f * data[0] : 1.055f * pow(data[0], 1.0f / 2.4f) - 0.055f;
|
||||
}
|
||||
// 转换为线性RGB
|
||||
T linearRGB() const {
|
||||
return data[0] <= 0.04045f ? data[0] / 12.92f : pow((data[0] + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
// 转换为YCbCr
|
||||
T YCbCr() const {
|
||||
return 0.299f * data[0] + 0.587f * data[1] + 0.114f * data[2];
|
||||
}
|
||||
// 转换为YUV
|
||||
T YUV() const {
|
||||
return 0.299f * data[0] + 0.587f * data[1] + 0.114f * data[2];
|
||||
}
|
||||
|
||||
T operator+(const pixel& rhs) const {
|
||||
pixel result;
|
||||
for (int i = 0; i < N; ++i) {
|
||||
result.data[i] = data[i] + rhs.data[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
T operator-(const pixel& rhs) const {
|
||||
pixel result;
|
||||
for (int i = 0; i < N; ++i) {
|
||||
result.data[i] = data[i] - rhs.data[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
T operator*(const pixel& rhs) const {
|
||||
pixel result;
|
||||
for (int i = 0; i < N; ++i) {
|
||||
result.data[i] = data[i] * rhs.data[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
T operator/(const pixel& rhs) const {
|
||||
pixel result;
|
||||
for (int i = 0; i < N; ++i) {
|
||||
result.data[i] = data[i] / rhs.data[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
T& operator+=(const pixel& rhs) {
|
||||
for (int i = 0; i < N; ++i) {
|
||||
data[i] += rhs.data[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
T& operator-=(const pixel& rhs) {
|
||||
for (int i = 0; i < N; ++i) {
|
||||
data[i] -= rhs.data[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
T& operator*=(const pixel& rhs) {
|
||||
for (int i = 0; i < N; ++i) {
|
||||
data[i] *= rhs.data[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
T& operator/=(const pixel& rhs) {
|
||||
for (int i = 0; i < N; ++i) {
|
||||
data[i] /= rhs.data[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U, int N2>
|
||||
auto& operator=(const pixel<U, N2>& rhs) {
|
||||
constexpr int Num = std::min(N, N2);
|
||||
memset(data, 0, sizeof(data));
|
||||
for (int i = 0; i < Num; ++i) {
|
||||
data[i] = color_convert<U, T>(rhs.data[i]);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U, int N2>
|
||||
explicit operator pixel<U, N2>() {
|
||||
pixel<U, N2> result = *this;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// 图像访问器
|
||||
template<typename P>
|
||||
struct image_accessor {
|
||||
using pixel_type = typename P::pixel_type;
|
||||
|
||||
image_accessor(void* in_data, const int in_width, const int in_height)
|
||||
: data(in_data), width(in_width), height(in_height), row_pitch(in_width * sizeof(P)) {}
|
||||
|
||||
template<typename T>
|
||||
image_accessor(void* in_data, const Eigen::Vector2<T>& in_size)
|
||||
: data(in_data), width(in_size.x()), height(in_size.y()), row_pitch(in_size.x() * sizeof(P)) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将另一个图片复制到此图片
|
||||
* @tparam T 源像素格式
|
||||
* @param rhs 源图像
|
||||
*/
|
||||
template<typename T>
|
||||
void copy_from(const image_accessor<T>& rhs) {
|
||||
const int temp_width = std::min(width, rhs.width);
|
||||
const int temp_height = std::min(height, rhs.height);
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
auto row_size = std::min(temp_width * sizeof(T), row_pitch);
|
||||
for (int y = 0; y < temp_height; ++y) {
|
||||
auto src_row = rhs.get_row(y);
|
||||
auto dst_row = get_row(y);
|
||||
memcpy(dst_row, data, row_size);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int y = 0; y < temp_height; ++y) {
|
||||
for (int x = 0; x < temp_width; ++x) {
|
||||
get_pixel(x, y) = rhs.get_pixel(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将另一个图片复制到此图片, 从源图像的指定位置开始
|
||||
* @tparam T 源像素格式
|
||||
* @param rhs 源图像
|
||||
* @param rhs_pos 从源图像的此处开始复制
|
||||
*/
|
||||
template<typename T>
|
||||
void copy_from(const image_accessor<T>& rhs, const Eigen::Vector2i& rhs_pos) {
|
||||
const int temp_width = std::min(width, rhs.width - rhs_pos.x());
|
||||
const int temp_height = std::min(height, rhs.height - rhs_pos.y());
|
||||
for (int y = 0; y < temp_height; ++y) {
|
||||
for (int x = 0; x < temp_width; ++x) {
|
||||
get_pixel(x, y) = rhs.get_pixel(x + rhs_pos.x(), y + rhs_pos.y());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将另一个图片复制到此图片, 从此图片偏移开始
|
||||
* @tparam T 源像素格式
|
||||
* @param rhs 源图像
|
||||
* @param in_start 从此处开始复制
|
||||
*/
|
||||
template<typename T>
|
||||
void offset_copy_from(const image_accessor<T>& rhs, const Eigen::Vector2i& in_start) {
|
||||
const int temp_width = std::min(width - in_start.x(), rhs.width);
|
||||
const int temp_height = std::min(height - in_start.y(), rhs.height);
|
||||
if constexpr (std::is_same<P, T>::value) {
|
||||
for (int y = 0; y < temp_height; ++y) {
|
||||
auto src_row = rhs.get_row(y);
|
||||
auto dst_row = get_row(y + in_start.y()) + in_start.x() * sizeof(T);
|
||||
memcpy(dst_row, src_row, temp_width * sizeof(T));
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int y = 0; y < temp_height; ++y) {
|
||||
for (int x = 0; x < temp_width; ++x) {
|
||||
get_pixel(x + in_start.x(), y + in_start.y()) = rhs.get_pixel(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
P& get_pixel(const int x, const int y) {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) {
|
||||
throw std::out_of_range("Pixel access out of bounds.");
|
||||
}
|
||||
const int pixel_size = sizeof(P);
|
||||
const int offset = y * row_pitch + x * pixel_size;
|
||||
auto data_ptr = static_cast<uint8_t*>(data) + offset;
|
||||
|
||||
return *reinterpret_cast<P*>(data_ptr);
|
||||
}
|
||||
const P& get_pixel(const int x, const int y) const {
|
||||
return const_cast<image_accessor*>(this)->get_pixel(x, y);
|
||||
}
|
||||
|
||||
P& operator()(const int x, const int y) {
|
||||
return get_pixel(x, y);
|
||||
}
|
||||
const P& operator()(const int x, const int y) const {
|
||||
return get_pixel(x, y);
|
||||
}
|
||||
P& operator()(const Eigen::Vector2i& pos) {
|
||||
return get_pixel(pos.x(), pos.y());
|
||||
}
|
||||
P& operator()(const Eigen::Vector2i& pos) const {
|
||||
return get_pixel(pos.x(), pos.y());
|
||||
}
|
||||
|
||||
void set_row_pitch(const uint64_t in_row_pitch) {
|
||||
assert(in_row_pitch >= width);
|
||||
row_pitch = in_row_pitch;
|
||||
}
|
||||
void flip_y() {
|
||||
std::vector<uint8_t> temp_row(row_pitch); // 临时存储一行数据
|
||||
|
||||
for (size_t y = 0; y < height / 2; ++y) {
|
||||
// 计算要交换的两行的起始地址
|
||||
uint8_t* top_row = (uint8_t*)data + y * row_pitch;
|
||||
uint8_t* bottom_row = (uint8_t*)data + (height - 1 - y) * row_pitch;
|
||||
|
||||
// 交换两行数据
|
||||
std::memcpy(temp_row.data(), top_row, row_pitch); // 将 top_row 保存到临时缓冲区
|
||||
std::memcpy(top_row, bottom_row, row_pitch); // 将 bottom_row 移动到 top_row
|
||||
std::memcpy(bottom_row, temp_row.data(), row_pitch); // 将临时缓冲区的数据移动到 bottom_row
|
||||
}
|
||||
}
|
||||
|
||||
void* const data;
|
||||
const int width;
|
||||
const int height;
|
||||
uint64_t row_pitch; // 行跨度,支持对齐
|
||||
private:
|
||||
[[nodiscard]] uint8_t* get_row(const int y) const {
|
||||
return (uint8_t*)data + y * row_pitch;
|
||||
}
|
||||
};
|
||||
|
||||
using pixel_r8 = pixel<uint8_t, 1>;
|
||||
using pixel_rg8 = pixel<uint8_t, 2>;
|
||||
using pixel_rgb8 = pixel<uint8_t, 3>;
|
||||
using pixel_rgba8 = pixel<uint8_t, 4>;
|
||||
using pixel_a8 = pixel<uint8_t, 1>;
|
||||
|
||||
using pixel_r16 = pixel<uint16_t, 1>;
|
||||
using pixel_rg16 = pixel<uint16_t, 2>;
|
||||
using pixel_rgb16 = pixel<uint16_t, 3>;
|
||||
using pixel_rgba16 = pixel<uint16_t, 4>;
|
||||
using pixel_a16 = pixel<uint16_t, 1>;
|
||||
|
||||
using pixel_r16f = pixel<Eigen::half, 1>;
|
||||
using pixel_rg16f = pixel<Eigen::half, 2>;
|
||||
using pixel_rgb16f = pixel<Eigen::half, 3>;
|
||||
using pixel_rgba16f = pixel<Eigen::half, 4>;
|
||||
using pixel_a16f = pixel<Eigen::half, 1>;
|
||||
|
||||
using pixel_r = pixel<float, 1>;
|
||||
using pixel_rg = pixel<float, 2>;
|
||||
using pixel_rgb = pixel<float, 3>;
|
||||
using pixel_rgba = pixel<float, 4>;
|
||||
using pixel_a = pixel<float, 1>;
|
||||
}
|
30
src/core/misc/scope_exit.h
Normal file
30
src/core/misc/scope_exit.h
Normal file
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
namespace mirage {
|
||||
template<typename FuncType>
|
||||
class scope_exit_guard {
|
||||
public:
|
||||
scope_exit_guard(FuncType&& in_func) : func((FuncType&&)in_func) {
|
||||
}
|
||||
virtual ~scope_exit_guard() {
|
||||
func();
|
||||
}
|
||||
private:
|
||||
FuncType func;
|
||||
};
|
||||
|
||||
struct scope_exit_syntax_support {
|
||||
virtual ~scope_exit_syntax_support() = default;
|
||||
template <typename FuncType>
|
||||
scope_exit_guard<FuncType> operator+(FuncType&& InFunc)
|
||||
{
|
||||
return scope_exit_guard<FuncType>((FuncType&&)InFunc);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#define PRIVATE_CONCATENATE_DETAIL(x, y) x##y
|
||||
#define PRIVATE_CONCATENATE(x, y) PRIVATE_CONCATENATE_DETAIL(x, y)
|
||||
|
||||
#define ON_SCOPE_EXIT const auto PRIVATE_CONCATENATE(scope_exit, __LINE__) = mirage::scope_exit_syntax_support() + [&]()
|
||||
|
7988
src/core/misc/stb_image.h
Normal file
7988
src/core/misc/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
170
src/core/misc/type_hash.h
Normal file
170
src/core/misc/type_hash.h
Normal file
@ -0,0 +1,170 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
inline uint32_t murmur_finalize32(uint32_t hash) {
|
||||
hash ^= hash >> 16;
|
||||
hash *= 0x85ebca6b;
|
||||
hash ^= hash >> 13;
|
||||
hash *= 0xc2b2ae35;
|
||||
hash ^= hash >> 16;
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines two hash values to get a third.
|
||||
* Note - this function is not commutative.
|
||||
*
|
||||
* This function cannot change for backward compatibility reasons.
|
||||
* You may want to choose HashCombineFast for a better in-memory hash combining function.
|
||||
*/
|
||||
[[nodiscard]] inline uint32_t hash_combine(uint32_t a, uint32_t c) {
|
||||
uint32_t b = 0x9e3779b9;
|
||||
a += b;
|
||||
|
||||
a -= b; a -= c; a ^= (c>>13);
|
||||
b -= c; b -= a; b ^= (a<<8);
|
||||
c -= a; c -= b; c ^= (b>>13);
|
||||
a -= b; a -= c; a ^= (c>>12);
|
||||
b -= c; b -= a; b ^= (a<<16);
|
||||
c -= a; c -= b; c ^= (b>>5);
|
||||
a -= b; a -= c; a ^= (c>>3);
|
||||
b -= c; b -= a; b ^= (a<<10);
|
||||
c -= a; c -= b; c ^= (b>>15);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines two hash values to get a third.
|
||||
* Note - this function is not commutative.
|
||||
*
|
||||
* WARNING! This function is subject to change and should only be used for creating
|
||||
* combined hash values which don't leave the running process,
|
||||
* e.g. GetTypeHash() overloads.
|
||||
*/
|
||||
[[nodiscard]] inline uint32_t hash_combine_fast(uint32_t a, uint32_t b) {
|
||||
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline uint32_t pointer_hash(const void* key) {
|
||||
// Ignoring the lower 4 bits since they are likely zero anyway.
|
||||
// Higher bits are more significant in 64 bit builds.
|
||||
const uintptr_t ptr_int = reinterpret_cast<uintptr_t>(key) >> 4;
|
||||
return murmur_finalize32((uint32_t)ptr_int);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline uint32_t pointer_hash(const void* key, uint32_t c) {
|
||||
// we can use HashCombineFast here because pointers are non-persistent
|
||||
return hash_combine_fast(pointer_hash(key), c);
|
||||
}
|
||||
|
||||
//
|
||||
// Hash functions for common types.
|
||||
//
|
||||
// WARNING! GetTypeHash result values are not expected to leave the running process.
|
||||
// Do not persist them to disk, send them to another running process or
|
||||
// expect them to be consistent across multiple runs.
|
||||
//
|
||||
template <
|
||||
typename ScalarType,
|
||||
std::enable_if_t<std::is_scalar_v<ScalarType> && !std::is_same_v<ScalarType, char32_t*> && !std::is_same_v<ScalarType, const char32_t*>>* = nullptr
|
||||
>
|
||||
[[nodiscard]] uint32_t get_type_hash(ScalarType value)
|
||||
{
|
||||
if constexpr (std::is_integral_v<ScalarType>)
|
||||
{
|
||||
if constexpr (sizeof(ScalarType) <= 4)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else if constexpr (sizeof(ScalarType) == 8)
|
||||
{
|
||||
return (uint32_t)value + ((uint32_t)(value >> 32) * 23);
|
||||
}
|
||||
else if constexpr (sizeof(ScalarType) == 16)
|
||||
{
|
||||
const uint64_t low = (uint64_t)value;
|
||||
const uint64_t high = (uint64_t)(value >> 64);
|
||||
return get_type_hash(low) ^ get_type_hash(high);
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(sizeof(ScalarType) == 0, "Unsupported integral type");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_floating_point_v<ScalarType>)
|
||||
{
|
||||
if constexpr (std::is_same_v<ScalarType, float>)
|
||||
{
|
||||
return *(uint32_t*)&value;
|
||||
}
|
||||
else if constexpr (std::is_same_v<ScalarType, double>)
|
||||
{
|
||||
return get_type_hash(*(uint64_t*)&value);
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(sizeof(ScalarType) == 0, "Unsupported floating point type");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_enum_v<ScalarType>)
|
||||
{
|
||||
return get_type_hash((__underlying_type(ScalarType))value);
|
||||
}
|
||||
else if constexpr (std::is_pointer_v<ScalarType>)
|
||||
{
|
||||
// Once the TCHAR* deprecations below are removed, we want to prevent accidental string hashing, so this static_assert should be commented back in
|
||||
//static_assert(!TIsCharType<std::remove_pointer_t<ScalarType>>::Value, "Pointers to string types should use a PointerHash() or FCrc::Stricmp_DEPRECATED() call depending on requirements");
|
||||
|
||||
return pointer_hash(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(sizeof(ScalarType) == 0, "Unsupported scalar type");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
uint32_t N,
|
||||
std::enable_if_t<!std::is_same_v<const T, const char32_t>>* = nullptr
|
||||
>
|
||||
uint32_t get_type_hash(T (&array)[N])
|
||||
{
|
||||
return pointer_hash(array);
|
||||
}
|
||||
|
||||
// template <
|
||||
// typename T,
|
||||
// std::enable_if_t<std::is_same_v<const T, const char32_t>>* = nullptr
|
||||
// >
|
||||
// uint32_t get_type_hash(T* value)
|
||||
// {
|
||||
// // Hashing a TCHAR* array differently from a void* is dangerous and is deprecated.
|
||||
// // When removing these overloads post-deprecation, comment in the related static_assert in the std::is_pointer_v block of the GetTypeHash overload above.
|
||||
// return FCrc::Strihash_DEPRECATED(value);
|
||||
// }
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] uint32_t get_array_hash(const T* ptr, uint64_t size, uint32_t previous_hash = 0)
|
||||
{
|
||||
uint32_t result = previous_hash;
|
||||
while (size)
|
||||
{
|
||||
result = hash_combine_fast(result, get_type_hash(*ptr));
|
||||
++ptr;
|
||||
--size;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Use this when inside type that has get_type_hash() (no in-parameters) implemented. It makes GetTypeHash dispatch in global namespace
|
||||
template <typename T>
|
||||
[[nodiscard]] uint32_t get_type_hash_helper(const T& V) { return get_type_hash(V); }
|
157
src/core/shaders/SDF.slang
Normal file
157
src/core/shaders/SDF.slang
Normal file
@ -0,0 +1,157 @@
|
||||
// Push constants block
|
||||
cbuffer PushConstants : register(b0)
|
||||
{
|
||||
float2 window_extent;
|
||||
float2 viewport_scale;
|
||||
float2 red_subpixel_orientation;
|
||||
float2 blue_subpixel_orientation;
|
||||
bool has_subpixels;
|
||||
}
|
||||
|
||||
// Specialization constants
|
||||
static const float sdf_max_distance : SPECIALIZE_CONSTANT = 1.0;
|
||||
static const float atlas_image_width : SPECIALIZE_CONSTANT = 1.0;
|
||||
|
||||
// Sampler and textures
|
||||
// Texture2D in_textures[128] : register(t0);
|
||||
Texture2DArray in_textures_array : register(t0);
|
||||
SamplerState in_sampler : register(s0);
|
||||
|
||||
// Input structure for vertex shader
|
||||
struct VSInput
|
||||
{
|
||||
float3 in_position : POSITION;
|
||||
float4 in_clipping_rectangle : TEXCOORD0;
|
||||
float3 in_texture_coord : TEXCOORD1;
|
||||
float4 in_color : TEXCOORD2;
|
||||
};
|
||||
|
||||
// Output structure for vertex shader (input for pixel shader)
|
||||
struct VSOutput
|
||||
{
|
||||
float4 gl_Position : SV_POSITION;
|
||||
float4 out_clipping_rectangle : TEXCOORD0;
|
||||
float3 out_texture_coord : TEXCOORD1;
|
||||
float4 out_color : TEXCOORD2;
|
||||
float4 out_color_sqrt_rgby : TEXCOORD3;
|
||||
};
|
||||
|
||||
// Output structure for pixel shader
|
||||
struct PSOutput
|
||||
{
|
||||
float4 out_color : SV_Target0;
|
||||
float4 out_blend_factor : SV_Target1;
|
||||
};
|
||||
|
||||
// Include utility functions (you need to provide the utility functions from utils_vulkan.glsl)
|
||||
#include "utils_vulkan.slang"
|
||||
|
||||
// ---- Utility Functions (Shared between Vertex and Fragment) ----
|
||||
|
||||
// Function to convert position to viewport space
|
||||
float4 convert_position_to_viewport(float3 window_position)
|
||||
{
|
||||
float x = window_position.x * viewport_scale.x - 1.0;
|
||||
float y = (window_extent.y - window_position.y) * viewport_scale.y - 1.0;
|
||||
return float4(x, y, 1.0 - window_position.z * 0.01, 1.0);
|
||||
}
|
||||
|
||||
// Function to convert clipping rectangle to screen space
|
||||
float4 convert_clipping_rectangle_to_screen(float4 clipping_rectangle)
|
||||
{
|
||||
return float4(
|
||||
clipping_rectangle.x,
|
||||
window_extent.y - clipping_rectangle.w,
|
||||
clipping_rectangle.z,
|
||||
window_extent.y - clipping_rectangle.y
|
||||
);
|
||||
}
|
||||
|
||||
// Function to calculate texture stride
|
||||
float4 get_texture_stride(VSOutput input)
|
||||
{
|
||||
float2 horizontal_texture_stride = ddx(input.out_texture_coord.xy);
|
||||
float2 vertical_texture_stride = ddy(input.out_texture_coord.xy);
|
||||
return float4(horizontal_texture_stride, vertical_texture_stride);
|
||||
}
|
||||
|
||||
float2 green_coord(float4 texture_stride, float2 coord)
|
||||
{
|
||||
return coord;
|
||||
}
|
||||
|
||||
float2 red_coord(float4 texture_stride, float2 coord)
|
||||
{
|
||||
float4 tmp = texture_stride * float4(red_subpixel_orientation.x, red_subpixel_orientation.y, red_subpixel_orientation.x, red_subpixel_orientation.y);
|
||||
return coord + tmp.xy + tmp.zw;
|
||||
}
|
||||
|
||||
float2 blue_coord(float4 texture_stride, float2 coord)
|
||||
{
|
||||
float4 tmp = texture_stride * float4(blue_subpixel_orientation.x, blue_subpixel_orientation.y, blue_subpixel_orientation.x, blue_subpixel_orientation.y);
|
||||
return coord + tmp.xy + tmp.zw;
|
||||
}
|
||||
|
||||
// Calculate distance from sub-pixel to edge
|
||||
float3 get_subpixel_to_edge_distances(VSOutput input)
|
||||
{
|
||||
const int image_nr = int(input.out_texture_coord.z);
|
||||
float2 image_coord = input.out_texture_coord.xy;
|
||||
float4 texture_stride = get_texture_stride(input);
|
||||
|
||||
// float green_distance = in_textures[image_nr].Sample(in_sampler, green_coord(texture_stride, image_coord)).r;
|
||||
float green_distance = in_textures_array.Sample(in_sampler, float3(image_coord, image_nr)).r;
|
||||
|
||||
float3 distances = float3(green_distance, green_distance, green_distance);
|
||||
if (has_subpixels) {
|
||||
// distances.r = in_textures[image_nr].Sample(in_sampler, red_coord(texture_stride, image_coord)).r;
|
||||
// distances.b = in_textures[image_nr].Sample(in_sampler, blue_coord(texture_stride, image_coord)).r;
|
||||
distances.r = in_textures_array.Sample(in_sampler, float3(red_coord(texture_stride, image_coord), image_nr)).r;
|
||||
distances.b = in_textures_array.Sample(in_sampler, float3(blue_coord(texture_stride, image_coord), image_nr)).r;
|
||||
}
|
||||
|
||||
float pixel_distance = length(texture_stride.xy);
|
||||
float distance_multiplier = sdf_max_distance / (pixel_distance * atlas_image_width);
|
||||
return distances * distance_multiplier;
|
||||
}
|
||||
|
||||
[shader("vertex")]
|
||||
VSOutput vertex_main(VSInput input)
|
||||
{
|
||||
VSOutput output;
|
||||
|
||||
output.gl_Position = convert_position_to_viewport(input.in_position);
|
||||
output.out_clipping_rectangle = convert_clipping_rectangle_to_screen(input.in_clipping_rectangle);
|
||||
output.out_texture_coord = input.in_texture_coord;
|
||||
|
||||
float4 color = multiply_alpha(input.in_color);
|
||||
output.out_color = color;
|
||||
output.out_color_sqrt_rgby = sqrt(clamp(rgb_to_rgby(color.rgb), 0.0, 1.0));
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
[shader("pixel")]
|
||||
PSOutput pixel_main(VSOutput input)
|
||||
{
|
||||
PSOutput output;
|
||||
|
||||
// Check if fragment is within the clipping rectangle
|
||||
if (!contains(input.out_clipping_rectangle, input.gl_Position.xy)) {
|
||||
discard;
|
||||
}
|
||||
|
||||
float3 distances = get_subpixel_to_edge_distances(input);
|
||||
float3 coverage = clamp(distances + 0.5, 0.0, 1.0);
|
||||
|
||||
if (all(coverage == float3(0.0, 0.0, 0.0))) {
|
||||
discard;
|
||||
}
|
||||
|
||||
float4 alpha = coverage_to_alpha(float4(coverage, coverage.g), input.out_color_sqrt_rgby);
|
||||
|
||||
output.out_color = float4(input.out_color * alpha);
|
||||
output.out_blend_factor = input.out_color.a * alpha;
|
||||
|
||||
return output;
|
||||
}
|
27
src/core/shaders/aorii_rect.slang
Normal file
27
src/core/shaders/aorii_rect.slang
Normal file
@ -0,0 +1,27 @@
|
||||
#include "aorii_util.slang"
|
||||
|
||||
struct ParamBuffer
|
||||
{
|
||||
matrix transform;
|
||||
};
|
||||
ParameterBlock<ParamBuffer> param_buffer : register(b0);
|
||||
|
||||
struct PSInput {
|
||||
float4 position : SV_POSITION; // 裁剪空间坐标
|
||||
float4 color : COLOR; // 颜色
|
||||
};
|
||||
|
||||
[shader("vertex")]
|
||||
PSInput vertex_main(VSInput input)
|
||||
{
|
||||
PSInput output;
|
||||
output.position = mul(float4(input.position, 0.0, 1.0), param_buffer.transform);
|
||||
output.color = input.color;
|
||||
return output;
|
||||
}
|
||||
|
||||
[shader("pixel")]
|
||||
float4 pixel_main(PSInput input) : SV_TARGET
|
||||
{
|
||||
return input.color;
|
||||
}
|
52
src/core/shaders/aorii_rounded_rect.slang
Normal file
52
src/core/shaders/aorii_rounded_rect.slang
Normal file
@ -0,0 +1,52 @@
|
||||
#include "aorii_util.slang"
|
||||
|
||||
cbuffer ParamBuffer : register(b0)
|
||||
{
|
||||
matrix transform;
|
||||
float2 size; // 矩形大小 像素单位
|
||||
float2 pos; // 矩形位置 像素单位
|
||||
float4 radius; // 四角圆角像素单位 左上 右上 左下 右下
|
||||
};
|
||||
|
||||
struct PSInput {
|
||||
float4 position : SV_POSITION; // 裁剪空间坐标
|
||||
float2 uv : TEXCOORD0; // 纹理坐标
|
||||
float4 color : COLOR; // 颜色
|
||||
};
|
||||
|
||||
[shader("vertex")]
|
||||
PSInput vertex_main(VSInput input)
|
||||
{
|
||||
PSInput output;
|
||||
output.position = mul(float4(input.position, 0.0, 1.0), transform);
|
||||
output.uv = input.uv;
|
||||
output.color = input.color;
|
||||
return output;
|
||||
}
|
||||
|
||||
float distance_from_rect_uv(float2 p, float corner_radius) {
|
||||
corner_radius *= 2;
|
||||
float2 corner_radius_uv = corner_radius / size;
|
||||
float2 inner_rect = float2(1) - corner_radius_uv; // 圆心
|
||||
float2 q = abs(p) - inner_rect;
|
||||
q *= size;
|
||||
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - corner_radius;
|
||||
}
|
||||
|
||||
[shader("pixel")]
|
||||
float4 pixel_main(PSInput input) : SV_Target
|
||||
{
|
||||
float2 p = uv_to_ndc(input.uv);
|
||||
|
||||
// 象限
|
||||
int2 quadrant = sign(p);
|
||||
int idx = (quadrant.x > 0 ? 1 : 0) + (quadrant.y > 0 ? 2 : 0);
|
||||
float r = radius[idx];
|
||||
float d = distance_from_rect_uv(p, r);
|
||||
|
||||
float edge_width = fwidth(d);
|
||||
// 根据dd计算抗锯齿
|
||||
input.color.a *= smoothstep(0.0, edge_width, -d);
|
||||
|
||||
return input.color;
|
||||
}
|
60
src/core/shaders/aorii_sdf_text.slang
Normal file
60
src/core/shaders/aorii_sdf_text.slang
Normal file
@ -0,0 +1,60 @@
|
||||
#include "aorii_util.slang"
|
||||
|
||||
struct FontParams {
|
||||
float smoothing; // 平滑度
|
||||
float thickness; // 字体粗细
|
||||
float outline_width; // 描边宽度
|
||||
float4 outline_color; // 描边颜色
|
||||
}
|
||||
ParameterBlock<FontParams> font_param : register(b1);
|
||||
|
||||
struct PSInput {
|
||||
float4 position : SV_Position;
|
||||
float2 uv : TEXCOORD0;
|
||||
float4 color : COLOR0;
|
||||
float2 altas_uv : TEXCOORD1;
|
||||
float altas_index : TEXCOORD2;
|
||||
float2 char_size : TEXCOORD3;
|
||||
float range : TEXCOORD4;
|
||||
};
|
||||
|
||||
struct Constants {
|
||||
matrix transform;
|
||||
};
|
||||
ParameterBlock<Constants> param_buffer : register(b0);
|
||||
|
||||
[shader("vertex")]
|
||||
PSInput vertex_main(VSInput input) {
|
||||
float2 altas_uv = input.param_a.xy;
|
||||
float2 char_size = input.param_b.xy;
|
||||
|
||||
PSInput output;
|
||||
output.position = mul(float4(input.position, 0.0f, 1.0f), param_buffer.transform);
|
||||
output.uv = input.uv;
|
||||
output.color = input.color;
|
||||
output.altas_uv = altas_uv;
|
||||
output.altas_index = input.param_a.z;
|
||||
output.char_size = char_size;
|
||||
output.range = input.param_b.z;
|
||||
return output;
|
||||
}
|
||||
|
||||
Texture2DArray atlas_texture : register(t0);
|
||||
SamplerState sampler_state : register(s0);
|
||||
|
||||
[shader("pixel")]
|
||||
float4 pixel_main(PSInput input) : SV_Target {
|
||||
float2 uv = input.altas_uv + input.char_size * input.uv;
|
||||
float distance = atlas_texture.Sample(sampler_state, float3(uv, input.altas_index)).r;
|
||||
// return float4(distance, distance, distance, 1);
|
||||
float range = input.range;
|
||||
// if (range == 0) {
|
||||
// distance *= 1.25;
|
||||
// return float4(input.color.rgb, input.color.a * distance);
|
||||
// }
|
||||
float alpha = smoothstep(0.49 - range, 0.6 + range, distance);
|
||||
|
||||
float4 color = input.color;
|
||||
color.a *= alpha;
|
||||
return color;
|
||||
}
|
47
src/core/shaders/aorii_segment.slang
Normal file
47
src/core/shaders/aorii_segment.slang
Normal file
@ -0,0 +1,47 @@
|
||||
#include "aorii_util.slang"
|
||||
|
||||
struct PSInput {
|
||||
float4 position : SV_POSITION; // 裁剪空间坐标
|
||||
float2 uv : TEXCOORD0; // 纹理坐标
|
||||
float4 color : COLOR; // 颜色
|
||||
};
|
||||
|
||||
cbuffer segment_buffer : register(b0) {
|
||||
matrix transform;
|
||||
float2 pos_a; // 线段的起点
|
||||
float2 pos_b; // 线段的终点
|
||||
float thickness; // 线段的宽度
|
||||
};
|
||||
|
||||
[shader("vertex")]
|
||||
PSInput vertex_main(VSInput input) {
|
||||
PSInput output;
|
||||
output.position = mul(float4(input.position, 0.0, 1.0), transform);
|
||||
output.color = input.color;
|
||||
return output;
|
||||
}
|
||||
|
||||
// 计算点到线段的最短距mulnamespace离
|
||||
float sdf_line(float2 p, float2 a, float2 b) {
|
||||
float2 pa = p - a;
|
||||
float2 ba = b - a;
|
||||
|
||||
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
|
||||
return length(pa - ba * h);
|
||||
}
|
||||
|
||||
[shader("pixel")]
|
||||
float4 pixel_main(PSInput input) : SV_TARGET {
|
||||
// 将屏幕空间坐标转化为归一化设备坐标 (NDC)
|
||||
float2 p = input.position.xy; // 归一化设备坐标 (NDC)
|
||||
|
||||
// 计算当前片段到线段的距离
|
||||
float distance = sdf_line(p, pos_a, pos_b);
|
||||
|
||||
// 根据线段的粗细来决定颜色输出,使用抗锯齿处理
|
||||
float alpha = smoothstep(thickness, thickness + 1, distance);
|
||||
|
||||
// 最终输出颜色,带上 alpha 通道
|
||||
return float4(input.color.rgb, (1.0 - alpha) * input.color.a);
|
||||
}
|
||||
|
33
src/core/shaders/aorii_texture.slang
Normal file
33
src/core/shaders/aorii_texture.slang
Normal file
@ -0,0 +1,33 @@
|
||||
#include "aorii_util.slang"
|
||||
|
||||
struct ParamBuffer
|
||||
{
|
||||
matrix transform;
|
||||
};
|
||||
ParameterBlock<ParamBuffer> param_buffer;
|
||||
|
||||
struct PSInput {
|
||||
float4 position : SV_POSITION; // 裁剪空间坐标
|
||||
float2 uv : TEXCOORD0; // 纹理坐标
|
||||
float4 color : COLOR; // 颜色
|
||||
};
|
||||
|
||||
Texture2D<float4> texture;
|
||||
SamplerState sampler;
|
||||
|
||||
[shader("vertex")]
|
||||
PSInput vertex_main(VSInput input)
|
||||
{
|
||||
PSInput output;
|
||||
output.position = mul(float4(input.position, 0.0, 1.0), param_buffer.transform);
|
||||
output.uv = input.uv;
|
||||
output.color = input.color;
|
||||
return output;
|
||||
}
|
||||
|
||||
[shader("pixel")]
|
||||
float4 pixel_main(PSInput input) : SV_TARGET
|
||||
{
|
||||
return input.color * texture.Sample(sampler, input.uv);
|
||||
}
|
||||
|
13
src/core/shaders/aorii_util.slang
Normal file
13
src/core/shaders/aorii_util.slang
Normal file
@ -0,0 +1,13 @@
|
||||
struct VSInput {
|
||||
float2 position : POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
float4 color : COLOR0;
|
||||
float4 param_a : TEXCOORD1;
|
||||
float4 param_b : TEXCOORD2;
|
||||
float4 param_c : TEXCOORD3;
|
||||
};
|
||||
|
||||
// 将uv坐标系移动到[-1, -1] ~ [1, 1]
|
||||
float2 uv_to_ndc(float2 uv) {
|
||||
return uv * 2.0 - 1.0;
|
||||
}
|
9
src/core/shaders/test.frag
Normal file
9
src/core/shaders/test.frag
Normal file
@ -0,0 +1,9 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 fragColor;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = vec4(fragColor, 1.0);
|
||||
}
|
20
src/core/shaders/test.vert
Normal file
20
src/core/shaders/test.vert
Normal file
@ -0,0 +1,20 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) out vec3 fragColor;
|
||||
|
||||
vec2 positions[3] = vec2[](
|
||||
vec2(0.0, -0.5),
|
||||
vec2(0.5, 0.5),
|
||||
vec2(-0.5, 0.5)
|
||||
);
|
||||
|
||||
vec3 colors[3] = vec3[](
|
||||
vec3(1.0, 0.0, 0.0),
|
||||
vec3(0.0, 1.0, 0.0),
|
||||
vec3(0.0, 0.0, 1.0)
|
||||
);
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
|
||||
fragColor = colors[gl_VertexIndex];
|
||||
}
|
118
src/core/shaders/utils_vulkan.slang
Normal file
118
src/core/shaders/utils_vulkan.slang
Normal file
@ -0,0 +1,118 @@
|
||||
// Copyright Take Vos 2021.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
|
||||
|
||||
/** logical and.
|
||||
*/
|
||||
bool4 and(bool4 lhs, bool4 rhs)
|
||||
{
|
||||
return bool4(
|
||||
lhs.x && rhs.x,
|
||||
lhs.y && rhs.y,
|
||||
lhs.z && rhs.z,
|
||||
lhs.w && rhs.w
|
||||
);
|
||||
}
|
||||
|
||||
T mix<T:IFloat>(T x, T y, T a)
|
||||
{
|
||||
return x * (T(1) - a) + y * a;
|
||||
}
|
||||
|
||||
/** Check if the rectangle contains the point.
|
||||
*
|
||||
* @param rectangle A axis-aligned rectangle encoded as left-bottom=(x,y), right-top=(z,w)
|
||||
* @param point A 2D point
|
||||
* @return True if the point is inside the rectangle.
|
||||
*/
|
||||
bool contains(float4 rectangle, float2 point)
|
||||
{
|
||||
return all(point.xyxy >= rectangle == bool4(true, true, false, false));
|
||||
}
|
||||
|
||||
/** Convert coverage to a perceptual uniform alpha.
|
||||
*
|
||||
* This function takes into account the lightness of the full pixel, then
|
||||
* determines based on this if the background is either black or white, or
|
||||
* if linear conversion of coverage to alpha is needed.
|
||||
*
|
||||
* On black and white background we measure the target lightness of each sub-pixel
|
||||
* then convert to target luminosity and eventually the alpha value.
|
||||
*
|
||||
* The alpha-component of the return value is calculated based on the full pixel
|
||||
* lightness and from the green sub-pixel coverage.
|
||||
*
|
||||
* The full formula to convert coverage to alpha taking into account perceptual
|
||||
* uniform lightness between foreground and background colors:
|
||||
* ```
|
||||
* F = foreground color
|
||||
* B = background color
|
||||
* T = target color
|
||||
* c = coverage
|
||||
* a = alpha
|
||||
* T = mix(sqrt(F), sqrt(B), c) ^ 2
|
||||
*
|
||||
* a = (T - B) / (F - B) if F != B
|
||||
* a = c otherwise
|
||||
* ```
|
||||
*
|
||||
* To simplify this formula and remove the division we fill in the foreground and background
|
||||
* with black and white and the other way around:
|
||||
* ```
|
||||
* a = c^2 if F == 1 and B == 0
|
||||
* a = 2c - c^2 if F == 0 and B == 1
|
||||
* ```
|
||||
*
|
||||
* Now we mix based on the foreground color, expecting the background color to mirror.
|
||||
* ```
|
||||
* a = mix(2c - c^2, c^2, F^2) if B^2 == 1 - F^2
|
||||
* ```
|
||||
*
|
||||
* @param coverage The amount of coverage. Elements must be between 0.0 and 1.0
|
||||
* @param foreground_sq The sqrt of the foreground. Elements must be between 0.0 and 1.0
|
||||
* @return The alpha value for the red, blue, green, alpha color components.
|
||||
*/
|
||||
float coverage_to_alpha(float coverage, float sqrt_foreground)
|
||||
{
|
||||
float coverage_sq = coverage * coverage;
|
||||
float coverage_2 = coverage + coverage;
|
||||
return mix(coverage_2 - coverage_sq, coverage_sq, sqrt_foreground);
|
||||
}
|
||||
|
||||
/** Convert coverage to a perceptual uniform alpha.
|
||||
*
|
||||
* @see coverage_to_alpha(float, float)
|
||||
*/
|
||||
float4 coverage_to_alpha(float4 coverage, float4 sqrt_foreground)
|
||||
{
|
||||
float4 coverage_sq = coverage * coverage;
|
||||
float4 coverage_2 = coverage + coverage;
|
||||
return mix(coverage_2 - coverage_sq, coverage_sq, sqrt_foreground);
|
||||
}
|
||||
|
||||
/** Multiply the alpha with the color.
|
||||
*
|
||||
* @param color The color+alpha without pre-multiplication.
|
||||
* @return The color+alpha where the color is multiplied with the alpha.
|
||||
*/
|
||||
float4 multiply_alpha(float4 color)
|
||||
{
|
||||
return float4(color.rgb * color.a, color.a);
|
||||
}
|
||||
|
||||
/** Convert RGB to Y.
|
||||
*/
|
||||
float rgb_to_y(float3 color)
|
||||
{
|
||||
float3 tmp = color * float3(0.2126, 0.7152, 0.0722);
|
||||
return tmp.r + tmp.g + tmp.b;
|
||||
}
|
||||
|
||||
/** Convert RGB to RGBY.
|
||||
*/
|
||||
float4 rgb_to_rgby(float3 color)
|
||||
{
|
||||
return float4(color, rgb_to_y(color));
|
||||
}
|
112
src/core/shaders/utils_vulkan_glsl.glsl
Normal file
112
src/core/shaders/utils_vulkan_glsl.glsl
Normal file
@ -0,0 +1,112 @@
|
||||
// Copyright Take Vos 2021.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
/** logical and.
|
||||
*/
|
||||
bvec4 and(bvec4 lhs, bvec4 rhs)
|
||||
{
|
||||
return bvec4(
|
||||
lhs.x && rhs.x,
|
||||
lhs.y && rhs.y,
|
||||
lhs.z && rhs.z,
|
||||
lhs.w && rhs.w
|
||||
);
|
||||
}
|
||||
|
||||
/** Check if the rectangle contains the point.
|
||||
*
|
||||
* @param rectangle A axis-aligned rectangle encoded as left-bottom=(x,y), right-top=(z,w)
|
||||
* @param point A 2D point
|
||||
* @return True is the point is inside the rectangle.
|
||||
*/
|
||||
bool contains(vec4 rectangle, vec2 point)
|
||||
{
|
||||
return greaterThanEqual(point.xyxy, rectangle) == bvec4(true, true, false, false);
|
||||
}
|
||||
|
||||
/** Convert coverage to a perceptional uniform alpha.
|
||||
*
|
||||
* This function takes into account the lightness of the full pixel, then
|
||||
* determines based on this if the background is either black or white, or
|
||||
* if linear conversion of coverage to alpha is needed.
|
||||
*
|
||||
* On black and white background we measure the target lightness of each sub-pixel
|
||||
* then convert to target luminosity and eventually the alpha value.
|
||||
*
|
||||
* The alpha-component of the return value is calculated based on the full pixel
|
||||
* lightness and from the green sub-pixel coverage.
|
||||
*
|
||||
* The full formula to convert coverage to alpha taking into account perceptional
|
||||
* uniform lightness between foreground and background colors:
|
||||
* ```
|
||||
* F = foreground color
|
||||
* B = background color
|
||||
* T = target color
|
||||
* c = coverage
|
||||
* a = alpha
|
||||
* T = mix(sqrt(F), sqrt(B), c) ^ 2
|
||||
*
|
||||
* a = (T - B) / (F - B) if F != B
|
||||
* a = c otherwise
|
||||
* ```
|
||||
*
|
||||
* To simplify this formula and remove the division we fill in the foreground and background
|
||||
* with black and white and the other way around:
|
||||
* ```
|
||||
* a = c^2 if F == 1 and B == 0
|
||||
* a = 2c - c^2 if F == 0 and B == 1
|
||||
* ```
|
||||
*
|
||||
* Now we mix based on the foreground color, expecting the background color to mirror.
|
||||
* ```
|
||||
* a = mix(2c - c^2, c^2, F^2) if B^2 == 1 - F^2
|
||||
* ```
|
||||
*
|
||||
* @param coverage The amount of coverage. Elements must be between 0.0 and 1.0
|
||||
* @param foreground_sq The sqrt of the foreground. Elements must be between 0.0 and 1.0
|
||||
* @return The alpha value for the red, blue, green, alpha color components.
|
||||
*/
|
||||
float coverage_to_alpha(float coverage, float sqrt_foreground)
|
||||
{
|
||||
float coverage_sq = coverage * coverage;
|
||||
float coverage_2 = coverage + coverage;
|
||||
return mix(coverage_2 - coverage_sq, coverage_sq, sqrt_foreground);
|
||||
}
|
||||
|
||||
/** Convert coverage to a perceptional uniform alpha.
|
||||
*
|
||||
* @see coverage_to_alpha(float, float)
|
||||
*/
|
||||
vec4 coverage_to_alpha(vec4 coverage, vec4 sqrt_foreground)
|
||||
{
|
||||
vec4 coverage_sq = coverage * coverage;
|
||||
vec4 coverage_2 = coverage + coverage;
|
||||
return mix(coverage_2 - coverage_sq, coverage_sq, sqrt_foreground);
|
||||
}
|
||||
|
||||
/** Multiply the alpha with the color.
|
||||
*
|
||||
* @param color The color+alpha without pre-multiplication.
|
||||
* @return The color+alpha where the color is multiplied with the alpha.
|
||||
*/
|
||||
vec4 multiply_alpha(vec4 color)
|
||||
{
|
||||
return vec4(color.rgb * color.a, color.a);
|
||||
}
|
||||
|
||||
/** Convert RGB to Y.
|
||||
*/
|
||||
float rgb_to_y(vec3 color)
|
||||
{
|
||||
vec3 tmp = color * vec3(0.2126, 0.7152, 0.0722);
|
||||
return tmp.r + tmp.g + tmp.b;
|
||||
}
|
||||
|
||||
/** Convert RGB to RGBY.
|
||||
*/
|
||||
vec4 rgb_to_rgby(vec3 color)
|
||||
{
|
||||
return vec4(color, rgb_to_y(color));
|
||||
}
|
||||
|
1
third_party/LLGL
vendored
Submodule
1
third_party/LLGL
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 2ae011f65e795b56d8db83a478023301d81ac58e
|
1
third_party/msdfgen
vendored
Submodule
1
third_party/msdfgen
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 5a88b0c2b95033f7eee826b27c48d399d544d814
|
Loading…
x
Reference in New Issue
Block a user