This commit is contained in:
daiqingshuang 2025-02-07 19:10:00 +08:00 committed by Nanako
commit b7936d2e1a
46 changed files with 10741 additions and 0 deletions

55
.clang-format Normal file
View 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
View File

@ -0,0 +1,4 @@
/cmake-build-debug
/cmake-build-release
/.idea
/scripts/shader_paths.txt

6
.gitmodules vendored Normal file
View 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
View 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 ()

View 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
View 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 ()

View 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
View 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()

View 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
View 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
View 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
View 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
View 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 ()

View File

@ -0,0 +1 @@
#include "async_task.h"

View File

@ -0,0 +1,5 @@
#pragma once
class async_task {
};

View 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;
};
}

View 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;
}
}
}
}

View 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;
};
}

View 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_ */

View 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;
};

View 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
View 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
View 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
View 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
View 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;
};
}

View 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;
};
}

View 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{};
};
}

View 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
}

View 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
View 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>;
}

View 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

File diff suppressed because it is too large Load Diff

170
src/core/misc/type_hash.h Normal file
View 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
View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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);
}

View 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);
}

View 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;
}

View 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);
}

View 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];
}

View 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));
}

View 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

@ -0,0 +1 @@
Subproject commit 2ae011f65e795b56d8db83a478023301d81ac58e

1
third_party/msdfgen vendored Submodule

@ -0,0 +1 @@
Subproject commit 5a88b0c2b95033f7eee826b27c48d399d544d814