This commit is contained in:
Nanako 2025-02-06 18:52:52 +08:00
parent 7e90e0f302
commit ba993e294c
16 changed files with 313 additions and 123 deletions

View File

@ -7,6 +7,12 @@ if (APPLE)
enable_language(OBJC OBJCXX)
endif ()
#
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)

View File

@ -1,88 +1,16 @@
function(compile_slang_shaders input_file stage entry_point)
#
if (NOT DEFINED SHADER_OUTPUT_DIR)
message(FATAL_ERROR "SHADER_OUTPUT_DIR not defined")
endif ()
set(output_dir ${SHADER_OUTPUT_DIR})
# Python
find_program(PYTHON_EXECUTABLE python)
find_program(GLSLANG_VALIDATOR glslangValidator)
find_program(SLANG_COMPILER slangc)
#
get_filename_component(filename ${input_file} NAME_WE)
#
if(stage STREQUAL "vertex")
set(profile "vs_5_0")
elseif(stage STREQUAL "pixel")
set(profile "ps_5_0")
elseif (stage STREQUAL "comp")
set(profile "cs_5_0")
else()
message(FATAL_ERROR "Unsupported shader stage: ${stage}")
endif()
#
set(output_files)
set(extra_args ${ARGN})
# debug
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
list(APPEND extra_args "-g")
endif()
#
if(LLGL_BUILD_RENDERER_OPENGL OR LLGL_BUILD_RENDERER_OPENGL_ES3 OR LLGL_BUILD_RENDERER_WEBGL)
set(output_file ${output_dir}/${filename}_${entry_point}.glsl)
add_custom_command(
OUTPUT ${output_file}
COMMAND slangc -target glsl -entry ${entry_point} -stage ${stage} ${extra_args} -o ${output_file} ${input_file} -DUSE_OPENGL=1 -DUV_FLIP=1
DEPENDS ${input_file}
COMMENT "Compiling ${input_file} to GLSL (${stage}, ${entry_point})"
)
list(APPEND output_files ${output_file})
endif()
if(LLGL_BUILD_RENDERER_DIRECT3D11 OR LLGL_BUILD_RENDERER_DIRECT3D12)
set(output_file ${output_dir}/${filename}_${entry_point}.dxbc)
add_custom_command(
OUTPUT ${output_file}
COMMAND slangc -target dxbc -profile ${profile} -entry ${entry_point} -stage ${stage} ${extra_args} -o ${output_file} ${input_file} -DUSE_DX=1
DEPENDS ${input_file}
COMMENT "Compiling ${input_file} to DXBC (${stage}, ${entry_point}) with profile ${profile}"
)
list(APPEND output_files ${output_file})
endif()
if(LLGL_BUILD_RENDERER_VULKAN)
set(output_file ${output_dir}/${filename}_${entry_point}.spirv)
add_custom_command(
OUTPUT ${output_file}
COMMAND slangc -target spirv -entry ${entry_point} -stage ${stage} ${extra_args} -o ${output_file} ${input_file} -DUSE_VULKAN=1 -DUV_FLIP=1
DEPENDS ${input_file}
COMMENT "Compiling ${input_file} to SPIR-V (${stage}, ${entry_point})"
)
list(APPEND output_files ${output_file})
endif()
if(LLGL_BUILD_RENDERER_METAL)
set(output_file ${output_dir}/${filename}_${entry_point}.metal)
add_custom_command(
OUTPUT ${output_file}
COMMAND slangc -target metal -entry ${entry_point} -stage ${stage} ${extra_args} -o ${output_file} ${input_file} -DUSE_METAL=1
DEPENDS ${input_file}
COMMENT "Compiling ${input_file} to Metal Shading Language (${stage}, ${entry_point})"
)
list(APPEND output_files ${output_file})
endif()
#
set_property(GLOBAL APPEND PROPERTY ALL_SHADER_OUTPUTS ${output_files})
function(shader_compile_target INPUT_DIR)
#
list(APPEND SHADER_SOURCE_DIRS ${INPUT_DIR})
endfunction()
function(add_compile_shaders_target target_name before_target)
file(MAKE_DIRECTORY ${SHADER_OUTPUT_DIR})
get_property(ALL_SHADER_OUTPUTS GLOBAL PROPERTY ALL_SHADER_OUTPUTS)
add_custom_target(${target_name} ALL DEPENDS ${ALL_SHADER_OUTPUTS})
#
add_dependencies(${before_target} ${target_name})
# ALL_SHADER_OUTPUTS
set_property(GLOBAL PROPERTY ALL_SHADER_OUTPUTS "")
endfunction()
# , , scripts/compile_shaders.py
add_custom_target(compile_aorii_shaders
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/compile_shaders.py --input-dir ${SHADER_SOURCE_DIRS} --output-dir ${SHADER_OUTPUT_DIR}
COMMENT "Compiling shaders"
VERBATIM
)

100
scripts/compile_shaders.py Normal file
View File

@ -0,0 +1,100 @@
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 compile_glsl(input_file, output_dir, glslang_validator):
try:
base = os.path.splitext(os.path.basename(input_file))[0]
ext = os.path.splitext(input_file)[1][1:]
relative_path = os.path.relpath(os.path.dirname(input_file), args.input_dir)
output_subdir = os.path.join(output_dir, relative_path)
output_file = os.path.join(output_subdir, f"{base}.{ext}.spv")
os.makedirs(output_subdir, exist_ok=True)
cmd = [glslang_validator, "-V", input_file, "-o", output_file]
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
print(f"Compiled GLSL: {input_file} -> {output_file}")
return True
except subprocess.CalledProcessError as e:
print(f"Error compiling {input_file}:")
print(e.stderr)
return False
except Exception as e:
print(f"Unexpected error with {input_file}: {str(e)}")
return False
def find_slang_entries(input_file):
try:
with open(input_file, 'r') 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 compile_slang(input_file, output_dir, slangc):
try:
entries = find_slang_entries(input_file)
if not entries:
print(f"No entries found in {input_file}")
return False
base = os.path.splitext(os.path.basename(input_file))[0]
relative_path = os.path.relpath(os.path.dirname(input_file), args.input_dir)
output_subdir = os.path.join(output_dir, relative_path)
os.makedirs(output_subdir, exist_ok=True)
success = True
for entry in entries:
output_file = os.path.join(output_subdir, f"{base}_{entry}.spv")
cmd = [slangc, input_file, "-entry", entry, "-o", output_file, "-target", "spirv"]
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 shaders to SPIR-V")
parser.add_argument("--input-dir", required=True, help="Input directory containing shaders")
parser.add_argument("--output-dir", required=True, help="Output directory for SPIR-V files")
parser.add_argument("--glslang", default="glslangValidator", help="Path to glslangValidator")
parser.add_argument("--slangc", default="slangc", help="Path to slangc")
args = parser.parse_args()
glsl_ext = [".vert", ".frag", ".comp", ".geom", ".tesc", ".tese", ".glsl"]
slang_ext = [".slang"]
all_success = True
# Compile GLSL
for file in find_shader_files(args.input_dir, glsl_ext):
if not compile_glsl(file, args.output_dir, args.glang):
all_success = False
# Compile Slang
for file in find_shader_files(args.input_dir, slang_ext):
if not compile_slang(file, args.output_dir, args.slangc):
all_success = False
if not all_success:
sys.exit(1)
if __name__ == "__main__":
main()

View File

@ -27,20 +27,7 @@ if (APPLE)
endif ()
# shader
if (NOT DEFINED SHADER_OUTPUT_DIR)
set(SHADER_OUTPUT_DIR ${CMAKE_BINARY_DIR}/shaders CACHE PATH "Output directory for compiled shaders")
message(STATUS "SHADER_OUTPUT_DIR not defined, using default: ${SHADER_OUTPUT_DIR}")
endif ()
function(compile_aorii_shader SHADER_FILE)
compile_slang_shaders("${CMAKE_CURRENT_SOURCE_DIR}/shader/${SHADER_FILE}.slang" "vertex" "vertex_main")
compile_slang_shaders("${CMAKE_CURRENT_SOURCE_DIR}/shader/${SHADER_FILE}.slang" "pixel" "pixel_main")
endfunction()
compile_aorii_shader(aorii_rect)
compile_aorii_shader(aorii_rounded_rect)
compile_aorii_shader(aorii_texture)
compile_aorii_shader(aorii_segment)
compile_aorii_shader(aorii_sdf_text)
add_compile_shaders_target("compile_aorii_shaders" ${PROJECT_NAME})
shader_compile_target(${CMAKE_CURRENT_SOURCE_DIR}/shaders)
# example, shader
if (BUILD_EXAMPLE)
@ -50,10 +37,10 @@ if (BUILD_EXAMPLE)
# , compile_aorii_shaders, ${SOURCE_DIR}${DEST_DIR}
add_custom_command(
TARGET compile_aorii_shaders
TARGET ${PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${DEST_DIR}
COMMAND ${CMAKE_COMMAND} -E copy_directory ${SOURCE_DIR} ${DEST_DIR}
COMMENT "Copying shaders to ${DEST_DIR}"
)
endif ()
endif ()

View File

@ -46,12 +46,20 @@ namespace aorii {
}
int run(const init_info& in_init_info) {
if (!init(in_init_info.main_window_desc))
try {
if (!init(in_init_info.main_window_desc))
return -1;
while (!should_exit()) {
update();
}
destroy();
} catch (const std::exception& e) {
spdlog::error("运行时异常: {}", e.what());
return -1;
} catch (...) {
spdlog::error("未知异常");
return -1;
while (!should_exit()) {
update();
}
destroy();
return 0;
}

View File

@ -162,6 +162,7 @@ bool aorii_renderer::init() {
queue_create_info.setQueueCount(1);
queue_create_info.setPQueuePriorities(&queue_priority);
queue_create_infos.push_back(queue_create_info);
break;
}
vk::DeviceCreateInfo device_create_info{};
@ -182,6 +183,7 @@ bool aorii_renderer::init() {
}
void aorii_renderer::destroy() {
spdlog::info("销毁渲染器");
main_device->destroy();
instance.destroy();
}
@ -203,26 +205,21 @@ const std::vector<device_handle>& aorii_renderer::get_compute_device() const {
if (!compute_device.empty()) return compute_device;
for (const auto& device: selector.compute_group) {
const float queue_priority = 1.0f;
constexpr float queue_priority = 1.0f;
std::vector<vk::DeviceQueueCreateInfo> queue_create_infos;
const auto queue_families = device.getQueueFamilyProperties();
for (uint32_t i = 0; i < queue_families.size(); ++i) {
const bool queue_compute_support = static_cast<bool>(
queue_families[i].queueFlags & vk::QueueFlagBits::eCompute);
if (queue_compute_support) {
vk::DeviceQueueCreateInfo queue_create_info{};
queue_create_info.setQueueFamilyIndex(i);
queue_create_info.setQueueCount(1);
queue_create_info.setPQueuePriorities(&queue_priority);
queue_create_infos.push_back(queue_create_info);
}
const auto& families = device_selector::get_queue_families(device, vk::QueueFlagBits::eCompute);
for (const auto i: families) {
vk::DeviceQueueCreateInfo queue_create_info{};
queue_create_info.setQueueFamilyIndex(i);
queue_create_info.setQueueCount(1);
queue_create_info.setPQueuePriorities(&queue_priority);
queue_create_infos.push_back(queue_create_info);
}
device_handle handle;
vk::DeviceCreateInfo device_create_info{};
device_create_info.setQueueCreateInfos(queue_create_infos);
handle.create_device(device, device_create_info);
spdlog::info("创建 Vulkan 计算设备: {}", handle.get_physical_device_name());
compute_device.push_back(handle);
}
return compute_device;

View File

@ -19,6 +19,8 @@ public:
void destroy_surface(const vk::SurfaceKHR& in_surface) const;
device_handle get_main_device() const { return main_device; }
const std::vector<device_handle>& get_compute_device() const;
const auto& get_instance() const { return instance; }
private:
vk::Instance instance;
device_selector selector;

View File

@ -1,5 +1,6 @@
#pragma once
#include <vulkan/vulkan.hpp>
#include <spdlog/spdlog.h>
struct device_handle {
vk::PhysicalDevice physical_device;
@ -17,6 +18,7 @@ struct device_handle {
void create_device(const vk::PhysicalDevice in_physical_device, const vk::DeviceCreateInfo& in_create_info) {
physical_device = in_physical_device;
device = physical_device.createDevice(in_create_info);
spdlog::info("创建 Vulkan 设备: {}", get_physical_device_name());
}
void destroy_device() {
if (device) {

View File

@ -14,6 +14,7 @@ struct PSInput {
float4 color : COLOR; // 颜色
};
[shader("vertex")]
PSInput vertex_main(VSInput input)
{
PSInput output;
@ -32,6 +33,7 @@ float distance_from_rect_uv(float2 p, float corner_radius) {
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);

View File

@ -6,6 +6,7 @@ struct PSInput {
float4 color : COLOR; // 颜色
};
[[vk::binding(0), vk::set(0)]]
cbuffer segment_buffer : register(b0) {
matrix transform;
float2 pos_a; // 线段的起点

View File

@ -4,7 +4,7 @@ struct ParamBuffer
{
matrix transform;
};
ParameterBlock<ParamBuffer> param_buffer : register(b0);
ParameterBlock<ParamBuffer> param_buffer;
struct PSInput {
float4 position : SV_POSITION; // 裁剪空间坐标
@ -12,8 +12,8 @@ struct PSInput {
float4 color : COLOR; // 颜色
};
Texture2D<float4> texture : register(t0);
SamplerState sampler : register(s0);
Texture2D<float4> texture;
SamplerState sampler;
PSInput vertex_main(VSInput input)
{

View File

@ -0,0 +1,9 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
}

23
src/core/shader/test.vert Normal file
View File

@ -0,0 +1,23 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
out gl_PerVertex {
vec4 gl_Position;
};
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,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));
}

View File

@ -47,11 +47,21 @@ renderer_window::~renderer_window() {
}
}
void renderer_window::set_title(const std::wstring& in_title) const {
void renderer_window::set_title(const std::string& in_title) const {
glfwSetWindowTitle(window, in_title.c_str());
}
void renderer_window::set_size(int in_width, int in_height) {
glfwSetWindowSize(window, in_width, in_height);
on_resize(in_width, in_height);
}
void renderer_window::close() const {
glfwSetWindowShouldClose(window, GLFW_TRUE);
}
void renderer_window::update() {
// swapchain->;
}
bool renderer_window::create_surface() {

View File

@ -30,7 +30,8 @@ public:
explicit renderer_window(const window_desc& in_desc);
virtual ~renderer_window();
void set_title(const std::wstring& in_title) const;
void set_title(const std::string& in_title) const;
void set_size(int in_width, int in_height);
void close() const;
bool should_close() const { return glfwWindowShouldClose(window); }
@ -46,6 +47,8 @@ public:
return vk::Extent2D(width, height);
}
void update();
bool create_surface();
[[nodiscard]] auto get_surface() const { return surface; }
bool create_swap_chain(const renderer_swapchain::create_info& in_create_info);