Archived
1
Fork 0

Unify standalone and runtime shader compiler, rename old ShaderCompiler to MaterialCompiler

This commit is contained in:
redstrate 2020-08-15 20:09:16 -04:00
parent 9cd7860ab9
commit 80fd8a375b
16 changed files with 411 additions and 455 deletions

View file

@ -1,9 +1,9 @@
macro(compile_shader src) macro(compile_shader src)
string(REGEX REPLACE "\\.[^.]*$" "" MYFILE_WITHOUT_EXT ${src}) string(REGEX REPLACE "\\.[^.]*$" "" MYFILE_WITHOUT_EXT ${src})
set(SHADER_COMPILER_COMMAND $<TARGET_FILE:ShaderCompiler>) set(SHADER_COMPILER_COMMAND $<TARGET_FILE:ShaderCompilerTool>)
if(ENABLE_IOS) if(ENABLE_IOS)
set(SHADER_COMPILER_COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/../build/bin/Debug/ShaderCompiler") set(SHADER_COMPILER_COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/../build/bin/Debug/ShaderCompilerTool")
endif() endif()
set(EXTRA_PLATFORM_ARG "0") set(EXTRA_PLATFORM_ARG "0")
@ -36,7 +36,7 @@ function(add_shader)
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/shaders) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/shaders)
add_custom_target(BuildShaders DEPENDS ${SPV_FILES} ShaderCompiler) add_custom_target(BuildShaders DEPENDS ${SPV_FILES} ShaderCompilerTool)
add_dependencies(${add_shader_TARGET} BuildShaders) add_dependencies(${add_shader_TARGET} BuildShaders)
set(ALL_SHADER_FILES ${SPV_FILES} CACHE INTERNAL "" FORCE) set(ALL_SHADER_FILES ${SPV_FILES} CACHE INTERNAL "" FORCE)

View file

@ -5,6 +5,7 @@ add_subdirectory(gfx)
add_subdirectory(math) add_subdirectory(math)
add_subdirectory(log) add_subdirectory(log)
add_subdirectory(asset) add_subdirectory(asset)
add_subdirectory(shadercompiler)
if(NOT ENABLE_IOS AND NOT ENABLE_TVOS) if(NOT ENABLE_IOS AND NOT ENABLE_TVOS)
add_subdirectory(audio) add_subdirectory(audio)

View file

@ -7,8 +7,7 @@ set(SRC
include/imguipass.hpp include/imguipass.hpp
include/smaapass.hpp include/smaapass.hpp
include/scenecapture.hpp include/scenecapture.hpp
include/shadercompiler.hpp include/materialcompiler.hpp
include/DirStackIncluder.h
include/dofpass.hpp include/dofpass.hpp
src/renderer.cpp src/renderer.cpp
@ -17,7 +16,7 @@ set(SRC
src/imguipass.cpp src/imguipass.cpp
src/smaapass.cpp src/smaapass.cpp
src/scenecapture.cpp src/scenecapture.cpp
src/shadercompiler.cpp src/materialcompiler.cpp
src/dofpass.cpp) src/dofpass.cpp)
add_library(Renderer STATIC ${SRC}) add_library(Renderer STATIC ${SRC})
@ -32,11 +31,9 @@ target_link_libraries(Renderer
Core Core
imgui imgui
SMAA::SMAA SMAA::SMAA
${CROSS_LIBS}) ShaderCompiler)
target_include_directories(Renderer PUBLIC include) target_include_directories(Renderer PUBLIC include)
set_target_properties(Renderer PROPERTIES CXX_STANDARD 17) set_engine_properties(Renderer)
target_compile_features(Renderer PUBLIC cxx_std_17)
set_target_properties(Renderer PROPERTIES CXX_EXTENSIONS OFF)
include(../../cmake/BuildShaders.cmake) include(../../cmake/BuildShaders.cmake)

View file

@ -13,7 +13,7 @@ constexpr int tangent_buffer_index = 5;
constexpr int bitangent_buffer_index = 6; constexpr int bitangent_buffer_index = 6;
constexpr int bone_buffer_index = 7; constexpr int bone_buffer_index = 7;
class ShaderCompiler { class MaterialCompiler {
public: public:
GFXPipeline* create_static_pipeline(GFXGraphicsPipelineCreateInfo createInfo, bool positions_only = false, bool cubemap = false); GFXPipeline* create_static_pipeline(GFXGraphicsPipelineCreateInfo createInfo, bool positions_only = false, bool cubemap = false);
GFXPipeline* create_skinned_pipeline(GFXGraphicsPipelineCreateInfo createInfo, bool positions_only = false); GFXPipeline* create_skinned_pipeline(GFXGraphicsPipelineCreateInfo createInfo, bool positions_only = false);
@ -24,4 +24,4 @@ public:
std::variant<std::string, std::vector<uint32_t>> compile_material_fragment(Material& material, bool use_ibl = true); std::variant<std::string, std::vector<uint32_t>> compile_material_fragment(Material& material, bool use_ibl = true);
}; };
static ShaderCompiler shader_compiler; static MaterialCompiler material_compiler;

View file

@ -1,181 +1,10 @@
#include "shadercompiler.hpp" #include "materialcompiler.hpp"
#include <spirv_cpp.hpp>
#include <spirv_msl.hpp>
#include <SPIRV/GlslangToSpv.h>
#include "file.hpp" #include "file.hpp"
#include "log.hpp" #include "log.hpp"
#include "engine.hpp" #include "engine.hpp"
#include "string_utils.hpp" #include "string_utils.hpp"
#include "DirStackIncluder.h" #include "shadercompiler.hpp"
const TBuiltInResource DefaultTBuiltInResource = {
/* .MaxLights = */ 32,
/* .MaxClipPlanes = */ 6,
/* .MaxTextureUnits = */ 32,
/* .MaxTextureCoords = */ 32,
/* .MaxVertexAttribs = */ 64,
/* .MaxVertexUniformComponents = */ 4096,
/* .MaxVaryingFloats = */ 64,
/* .MaxVertexTextureImageUnits = */ 32,
/* .MaxCombinedTextureImageUnits = */ 80,
/* .MaxTextureImageUnits = */ 32,
/* .MaxFragmentUniformComponents = */ 4096,
/* .MaxDrawBuffers = */ 32,
/* .MaxVertexUniformVectors = */ 128,
/* .MaxVaryingVectors = */ 8,
/* .MaxFragmentUniformVectors = */ 16,
/* .MaxVertexOutputVectors = */ 16,
/* .MaxFragmentInputVectors = */ 15,
/* .MinProgramTexelOffset = */ -8,
/* .MaxProgramTexelOffset = */ 7,
/* .MaxClipDistances = */ 8,
/* .MaxComputeWorkGroupCountX = */ 65535,
/* .MaxComputeWorkGroupCountY = */ 65535,
/* .MaxComputeWorkGroupCountZ = */ 65535,
/* .MaxComputeWorkGroupSizeX = */ 1024,
/* .MaxComputeWorkGroupSizeY = */ 1024,
/* .MaxComputeWorkGroupSizeZ = */ 64,
/* .MaxComputeUniformComponents = */ 1024,
/* .MaxComputeTextureImageUnits = */ 16,
/* .MaxComputeImageUniforms = */ 8,
/* .MaxComputeAtomicCounters = */ 8,
/* .MaxComputeAtomicCounterBuffers = */ 1,
/* .MaxVaryingComponents = */ 60,
/* .MaxVertexOutputComponents = */ 64,
/* .MaxGeometryInputComponents = */ 64,
/* .MaxGeometryOutputComponents = */ 128,
/* .MaxFragmentInputComponents = */ 128,
/* .MaxImageUnits = */ 8,
/* .MaxCombinedImageUnitsAndFragmentOutputs = */ 8,
/* .MaxCombinedShaderOutputResources = */ 8,
/* .MaxImageSamples = */ 0,
/* .MaxVertexImageUniforms = */ 0,
/* .MaxTessControlImageUniforms = */ 0,
/* .MaxTessEvaluationImageUniforms = */ 0,
/* .MaxGeometryImageUniforms = */ 0,
/* .MaxFragmentImageUniforms = */ 8,
/* .MaxCombinedImageUniforms = */ 8,
/* .MaxGeometryTextureImageUnits = */ 16,
/* .MaxGeometryOutputVertices = */ 256,
/* .MaxGeometryTotalOutputComponents = */ 1024,
/* .MaxGeometryUniformComponents = */ 1024,
/* .MaxGeometryVaryingComponents = */ 64,
/* .MaxTessControlInputComponents = */ 128,
/* .MaxTessControlOutputComponents = */ 128,
/* .MaxTessControlTextureImageUnits = */ 16,
/* .MaxTessControlUniformComponents = */ 1024,
/* .MaxTessControlTotalOutputComponents = */ 4096,
/* .MaxTessEvaluationInputComponents = */ 128,
/* .MaxTessEvaluationOutputComponents = */ 128,
/* .MaxTessEvaluationTextureImageUnits = */ 16,
/* .MaxTessEvaluationUniformComponents = */ 1024,
/* .MaxTessPatchComponents = */ 120,
/* .MaxPatchVertices = */ 32,
/* .MaxTessGenLevel = */ 64,
/* .MaxViewports = */ 16,
/* .MaxVertexAtomicCounters = */ 0,
/* .MaxTessControlAtomicCounters = */ 0,
/* .MaxTessEvaluationAtomicCounters = */ 0,
/* .MaxGeometryAtomicCounters = */ 0,
/* .MaxFragmentAtomicCounters = */ 8,
/* .MaxCombinedAtomicCounters = */ 8,
/* .MaxAtomicCounterBindings = */ 1,
/* .MaxVertexAtomicCounterBuffers = */ 0,
/* .MaxTessControlAtomicCounterBuffers = */ 0,
/* .MaxTessEvaluationAtomicCounterBuffers = */ 0,
/* .MaxGeometryAtomicCounterBuffers = */ 0,
/* .MaxFragmentAtomicCounterBuffers = */ 1,
/* .MaxCombinedAtomicCounterBuffers = */ 1,
/* .MaxAtomicCounterBufferSize = */ 16384,
/* .MaxTransformFeedbackBuffers = */ 4,
/* .MaxTransformFeedbackInterleavedComponents = */ 64,
/* .MaxCullDistances = */ 8,
/* .MaxCombinedClipAndCullDistances = */ 8,
/* .MaxSamples = */ 4,
/* .maxMeshOutputVerticesNV = */ 256,
/* .maxMeshOutputPrimitivesNV = */ 512,
/* .maxMeshWorkGroupSizeX_NV = */ 32,
/* .maxMeshWorkGroupSizeY_NV = */ 1,
/* .maxMeshWorkGroupSizeZ_NV = */ 1,
/* .maxTaskWorkGroupSizeX_NV = */ 32,
/* .maxTaskWorkGroupSizeY_NV = */ 1,
/* .maxTaskWorkGroupSizeZ_NV = */ 1,
/* .maxMeshViewCountNV = */ 4,
/* .maxDualSourceDrawBuffersEXT = */ 4,
/* .limits = */ TLimits{
/* .nonInductiveForLoops = */ true,
/* .whileLoops = */ true,
/* .doWhileLoops = */ true,
/* .generalUniformIndexing = */ true,
/* .generalAttributeMatrixVectorIndexing = */ true,
/* .generalVaryingIndexing = */ true,
/* .generalSamplerIndexing = */ true,
/* .generalVariableIndexing = */ true,
/* .generalConstantMatrixVectorIndexing = */ true,
}};
const std::vector<unsigned int> CompileGLSL(const std::string& filename, EShLanguage ShaderType, bool skinned, bool cubemap) {
std::string newString = "#version 430 core\n";
newString += "#extension GL_GOOGLE_include_directive : enable\n";
newString += "#extension GL_GOOGLE_cpp_style_line_directive : enable\n";
if(skinned)
newString += "#define BONE\n";
if(cubemap)
newString += "#define CUBEMAP\n";
newString += "#line 1\n";
newString += filename;
const char* InputCString = newString.c_str();
glslang::TShader Shader(ShaderType);
Shader.setStrings(&InputCString, 1);
int ClientInputSemanticsVersion = 100; // maps to, say, #define VULKAN 100
glslang::EShTargetClientVersion VulkanClientVersion = glslang::EShTargetVulkan_1_1;
glslang::EShTargetLanguageVersion TargetVersion = glslang::EShTargetSpv_1_0;
Shader.setEnvInput(glslang::EShSourceGlsl, ShaderType, glslang::EShClientVulkan, ClientInputSemanticsVersion);
Shader.setEnvClient(glslang::EShClientVulkan, VulkanClientVersion);
Shader.setEnvTarget(glslang::EShTargetSpv, TargetVersion);
TBuiltInResource Resources = DefaultTBuiltInResource;
EShMessages messages = (EShMessages) (EShMsgSpvRules);
DirStackFileIncluder includer;
includer.pushExternalLocalDirectory(file::get_domain_path(file::Domain::Internal).string());
if (!Shader.parse(&Resources, 100, false, (EShMessages)0, includer)) {
console::error(System::Renderer, "{}", Shader.getInfoLog());
return {};
}
glslang::TProgram Program;
Program.addShader(&Shader);
if(!Program.link(messages)) {
console::error(System::None, "Failed to link shader: {} {} {}", filename, Shader.getInfoLog(), Shader.getInfoDebugLog());
return {};
}
std::vector<unsigned int> SpirV;
spv::SpvBuildLogger logger;
glslang::SpvOptions spvOptions;
glslang::GlslangToSpv(*Program.getIntermediate(ShaderType), SpirV, &logger, &spvOptions);
return SpirV;
}
std::variant<std::string, std::vector<uint32_t>> get_shader(std::string filename, bool skinned, bool cubemap) { std::variant<std::string, std::vector<uint32_t>> get_shader(std::string filename, bool skinned, bool cubemap) {
auto shader_file = file::open(file::internal_domain / filename); auto shader_file = file::open(file::internal_domain / filename);
@ -184,31 +13,24 @@ std::variant<std::string, std::vector<uint32_t>> get_shader(std::string filename
return ""; return "";
} }
EShLanguage lang; ShaderStage stage;
if(filename.find("vert") != std::string::npos) { if(filename.find("vert") != std::string::npos) {
lang = EShLangVertex; stage = ShaderStage::Vertex;
} else { } else {
lang = EShLangFragment; stage = ShaderStage::Fragment;
} }
auto vertexSPIRV = CompileGLSL(shader_file->read_as_string(), lang, skinned, cubemap); CompileOptions options;
if(skinned)
options.add_definition("BONE");
#ifdef PLATFORM_MACOS if(cubemap)
spirv_cross::CompilerMSL msl(std::move(vertexSPIRV)); options.add_definition("CUBEMAP");
spirv_cross::CompilerMSL::Options opts; return shader_compiler.compile(ShaderLanguage::GLSL, stage, shader_file->read_as_string(), ShaderLanguage::MSL, options)->source;
opts.platform = spirv_cross::CompilerMSL::Options::Platform::macOS;
opts.enable_decoration_binding = true;
msl.set_msl_options(opts);
return msl.compile();
#else
return vertexSPIRV;
#endif
} }
GFXPipeline* ShaderCompiler::create_static_pipeline(GFXGraphicsPipelineCreateInfo createInfo, bool positions_only, bool cubemap) { GFXPipeline* MaterialCompiler::create_static_pipeline(GFXGraphicsPipelineCreateInfo createInfo, bool positions_only, bool cubemap) {
// take vertex src // take vertex src
std::string vertex_path = createInfo.shaders.vertex_path.data(); std::string vertex_path = createInfo.shaders.vertex_path.data();
vertex_path += ".glsl"; vertex_path += ".glsl";
@ -245,7 +67,7 @@ GFXPipeline* ShaderCompiler::create_static_pipeline(GFXGraphicsPipelineCreateInf
return engine->get_gfx()->create_graphics_pipeline(createInfo); return engine->get_gfx()->create_graphics_pipeline(createInfo);
} }
GFXPipeline* ShaderCompiler::create_skinned_pipeline(GFXGraphicsPipelineCreateInfo createInfo, bool positions_only) { GFXPipeline* MaterialCompiler::create_skinned_pipeline(GFXGraphicsPipelineCreateInfo createInfo, bool positions_only) {
createInfo.label += " (Skinned)"; createInfo.label += " (Skinned)";
// take vertex src // take vertex src
@ -290,9 +112,7 @@ GFXPipeline* ShaderCompiler::create_skinned_pipeline(GFXGraphicsPipelineCreateIn
return engine->get_gfx()->create_graphics_pipeline(createInfo); return engine->get_gfx()->create_graphics_pipeline(createInfo);
} }
std::tuple<GFXPipeline*, GFXPipeline*> ShaderCompiler::create_pipeline_permutations(GFXGraphicsPipelineCreateInfo& createInfo, bool positions_only) { std::tuple<GFXPipeline*, GFXPipeline*> MaterialCompiler::create_pipeline_permutations(GFXGraphicsPipelineCreateInfo& createInfo, bool positions_only) {
glslang::InitializeProcess();
auto st = create_static_pipeline(createInfo, positions_only); auto st = create_static_pipeline(createInfo, positions_only);
auto ss = create_skinned_pipeline(createInfo, positions_only); auto ss = create_skinned_pipeline(createInfo, positions_only);
@ -364,7 +184,7 @@ layout(push_constant, binding = 0) uniform PushConstant {\n \
int materialOffset;\n \ int materialOffset;\n \
};\n"; };\n";
std::variant<std::string, std::vector<uint32_t>> ShaderCompiler::compile_material_fragment(Material& material, bool use_ibl) { std::variant<std::string, std::vector<uint32_t>> MaterialCompiler::compile_material_fragment(Material& material, bool use_ibl) {
walked_nodes.clear(); walked_nodes.clear();
if(!render_options.enable_ibl) if(!render_options.enable_ibl)
@ -567,19 +387,5 @@ std::variant<std::string, std::vector<uint32_t>> ShaderCompiler::compile_materia
src += "}\n"; src += "}\n";
auto vertexSPIRV = CompileGLSL(src, EShLangFragment, false, false); return shader_compiler.compile(ShaderLanguage::GLSL, ShaderStage::Fragment, src, ShaderLanguage::MSL)->source;
#ifdef PLATFORM_MACOS
spirv_cross::CompilerMSL msl(std::move(vertexSPIRV));
spirv_cross::CompilerMSL::Options opts;
opts.platform = spirv_cross::CompilerMSL::Options::Platform::macOS;
opts.enable_decoration_binding = true;
msl.set_msl_options(opts);
return msl.compile();
#else
return vertexSPIRV;
#endif
} }

View file

@ -18,10 +18,11 @@
#include "shadowpass.hpp" #include "shadowpass.hpp"
#include "smaapass.hpp" #include "smaapass.hpp"
#include "scenecapture.hpp" #include "scenecapture.hpp"
#include "shadercompiler.hpp" #include "materialcompiler.hpp"
#include "assertions.hpp" #include "assertions.hpp"
#include "dofpass.hpp" #include "dofpass.hpp"
#include "frustum.hpp" #include "frustum.hpp"
#include "shadercompiler.hpp"
struct ElementInstance { struct ElementInstance {
Vector4 color; Vector4 color;
@ -83,6 +84,8 @@ struct UIPushConstant {
Renderer::Renderer(GFX* gfx, const bool enable_imgui) : gfx(gfx) { Renderer::Renderer(GFX* gfx, const bool enable_imgui) : gfx(gfx) {
Expects(gfx != nullptr); Expects(gfx != nullptr);
shader_compiler.set_include_path(file::get_domain_path(file::Domain::Internal).string());
createDummyTexture(); createDummyTexture();
shadow_pass = std::make_unique<ShadowPass>(gfx); shadow_pass = std::make_unique<ShadowPass>(gfx);
@ -691,19 +694,19 @@ void Renderer::create_mesh_pipeline(Material& material) {
pipelineInfo.blending.src_rgb = GFXBlendFactor::SrcAlpha; pipelineInfo.blending.src_rgb = GFXBlendFactor::SrcAlpha;
pipelineInfo.blending.dst_rgb = GFXBlendFactor::OneMinusSrcAlpha; pipelineInfo.blending.dst_rgb = GFXBlendFactor::OneMinusSrcAlpha;
pipelineInfo.shaders.fragment_src = shader_compiler.compile_material_fragment(material); pipelineInfo.shaders.fragment_src = material_compiler.compile_material_fragment(material);
pipelineInfo.shaders.fragment_path = ""; pipelineInfo.shaders.fragment_path = "";
auto [static_pipeline, skinned_pipeline] = shader_compiler.create_pipeline_permutations(pipelineInfo); auto [static_pipeline, skinned_pipeline] = material_compiler.create_pipeline_permutations(pipelineInfo);
material.static_pipeline = static_pipeline; material.static_pipeline = static_pipeline;
material.skinned_pipeline = skinned_pipeline; material.skinned_pipeline = skinned_pipeline;
pipelineInfo.render_pass = scene_capture->renderPass; pipelineInfo.render_pass = scene_capture->renderPass;
pipelineInfo.shaders.fragment_src = shader_compiler.compile_material_fragment(material, false); // scene capture does not use IBL pipelineInfo.shaders.fragment_src = material_compiler.compile_material_fragment(material, false); // scene capture does not use IBL
material.capture_pipeline = shader_compiler.create_static_pipeline(pipelineInfo, false, true); material.capture_pipeline = material_compiler.create_static_pipeline(pipelineInfo, false, true);
} }
void Renderer::createDummyTexture() { void Renderer::createDummyTexture() {

View file

@ -7,7 +7,7 @@
#include "engine.hpp" #include "engine.hpp"
#include "renderer.hpp" #include "renderer.hpp"
#include "shadowpass.hpp" #include "shadowpass.hpp"
#include "shadercompiler.hpp" #include "materialcompiler.hpp"
#include "frustum.hpp" #include "frustum.hpp"
struct PushConstant { struct PushConstant {

View file

@ -5,7 +5,7 @@
#include "gfx.hpp" #include "gfx.hpp"
#include "log.hpp" #include "log.hpp"
#include "engine.hpp" #include "engine.hpp"
#include "shadercompiler.hpp" #include "materialcompiler.hpp"
#include "assertions.hpp" #include "assertions.hpp"
#include "frustum.hpp" #include "frustum.hpp"
@ -325,7 +325,7 @@ void ShadowPass::create_pipelines() {
{ {
pipelineInfo.label = "Sun Shadow"; pipelineInfo.label = "Sun Shadow";
auto [static_pipeline, skinned_pipeline] = shader_compiler.create_pipeline_permutations(pipelineInfo, true); auto [static_pipeline, skinned_pipeline] = material_compiler.create_pipeline_permutations(pipelineInfo, true);
static_sun_pipeline = static_pipeline; static_sun_pipeline = static_pipeline;
skinned_sun_pipeline = skinned_pipeline; skinned_sun_pipeline = skinned_pipeline;
@ -337,7 +337,7 @@ void ShadowPass::create_pipelines() {
pipelineInfo.render_pass = cube_render_pass; pipelineInfo.render_pass = cube_render_pass;
auto [static_pipeline, skinned_pipeline] = shader_compiler.create_pipeline_permutations(pipelineInfo, true); auto [static_pipeline, skinned_pipeline] = material_compiler.create_pipeline_permutations(pipelineInfo, true);
static_spot_pipeline = static_pipeline; static_spot_pipeline = static_pipeline;
skinned_spot_pipeline = skinned_pipeline; skinned_spot_pipeline = skinned_pipeline;
@ -349,7 +349,7 @@ void ShadowPass::create_pipelines() {
pipelineInfo.shaders.fragment_path = "omnishadow.frag"; pipelineInfo.shaders.fragment_path = "omnishadow.frag";
auto [static_pipeline, skinned_pipeline] = shader_compiler.create_pipeline_permutations(pipelineInfo, true); auto [static_pipeline, skinned_pipeline] = material_compiler.create_pipeline_permutations(pipelineInfo, true);
static_point_pipeline = static_pipeline; static_point_pipeline = static_pipeline;
skinned_point_pipeline = skinned_pipeline; skinned_point_pipeline = skinned_pipeline;

View file

@ -0,0 +1,15 @@
set(SRC
include/shadercompiler.hpp
src/shadercompiler.cpp
src/includer.hpp
src/defaultresources.hpp)
add_library(ShaderCompiler STATIC ${SRC})
target_link_libraries(ShaderCompiler
PRIVATE
Utility
Log
${CROSS_LIBS})
target_include_directories(ShaderCompiler PUBLIC include PRIVATE src)
set_engine_properties(ShaderCompiler)

View file

@ -0,0 +1,59 @@
#pragma once
#include <variant>
#include <string>
#include <vector>
#include <string_view>
#include <optional>
enum class ShaderStage {
Vertex,
Fragment
};
enum class ShaderLanguage {
GLSL,
MSL,
SPIRV
};
class CompileOptions {
public:
void add_definition(const std::string_view name) {
definitions.emplace_back(name);
}
std::vector<std::string> definitions;
bool is_apple_mobile = false;
};
class ShaderSource {
public:
ShaderSource(const std::string source_string) : source(source_string) {}
ShaderSource(const std::vector<uint32_t> source_bytecode) : source(source_bytecode) {}
std::variant<std::string, std::vector<uint32_t>> source;
std::string_view as_string() const {
return std::get<std::string>(source);
}
std::vector<uint32_t> as_bytecode() const {
return std::get<std::vector<uint32_t>>(source);
}
};
class ShaderCompiler {
public:
ShaderCompiler();
void set_include_path(const std::string_view path);
/**
Compiles from one shader language to another shader language.
*/
std::optional<ShaderSource> compile(const ShaderLanguage from_language, const ShaderStage shader_stage, const ShaderSource& shader_source, const ShaderLanguage to_language, const CompileOptions& options = CompileOptions());
};
static inline ShaderCompiler shader_compiler;

View file

@ -0,0 +1,108 @@
#pragma once
const TBuiltInResource DefaultTBuiltInResource = {
/* .MaxLights = */ 32,
/* .MaxClipPlanes = */ 6,
/* .MaxTextureUnits = */ 32,
/* .MaxTextureCoords = */ 32,
/* .MaxVertexAttribs = */ 64,
/* .MaxVertexUniformComponents = */ 4096,
/* .MaxVaryingFloats = */ 64,
/* .MaxVertexTextureImageUnits = */ 32,
/* .MaxCombinedTextureImageUnits = */ 80,
/* .MaxTextureImageUnits = */ 32,
/* .MaxFragmentUniformComponents = */ 4096,
/* .MaxDrawBuffers = */ 32,
/* .MaxVertexUniformVectors = */ 128,
/* .MaxVaryingVectors = */ 8,
/* .MaxFragmentUniformVectors = */ 16,
/* .MaxVertexOutputVectors = */ 16,
/* .MaxFragmentInputVectors = */ 15,
/* .MinProgramTexelOffset = */ -8,
/* .MaxProgramTexelOffset = */ 7,
/* .MaxClipDistances = */ 8,
/* .MaxComputeWorkGroupCountX = */ 65535,
/* .MaxComputeWorkGroupCountY = */ 65535,
/* .MaxComputeWorkGroupCountZ = */ 65535,
/* .MaxComputeWorkGroupSizeX = */ 1024,
/* .MaxComputeWorkGroupSizeY = */ 1024,
/* .MaxComputeWorkGroupSizeZ = */ 64,
/* .MaxComputeUniformComponents = */ 1024,
/* .MaxComputeTextureImageUnits = */ 16,
/* .MaxComputeImageUniforms = */ 8,
/* .MaxComputeAtomicCounters = */ 8,
/* .MaxComputeAtomicCounterBuffers = */ 1,
/* .MaxVaryingComponents = */ 60,
/* .MaxVertexOutputComponents = */ 64,
/* .MaxGeometryInputComponents = */ 64,
/* .MaxGeometryOutputComponents = */ 128,
/* .MaxFragmentInputComponents = */ 128,
/* .MaxImageUnits = */ 8,
/* .MaxCombinedImageUnitsAndFragmentOutputs = */ 8,
/* .MaxCombinedShaderOutputResources = */ 8,
/* .MaxImageSamples = */ 0,
/* .MaxVertexImageUniforms = */ 0,
/* .MaxTessControlImageUniforms = */ 0,
/* .MaxTessEvaluationImageUniforms = */ 0,
/* .MaxGeometryImageUniforms = */ 0,
/* .MaxFragmentImageUniforms = */ 8,
/* .MaxCombinedImageUniforms = */ 8,
/* .MaxGeometryTextureImageUnits = */ 16,
/* .MaxGeometryOutputVertices = */ 256,
/* .MaxGeometryTotalOutputComponents = */ 1024,
/* .MaxGeometryUniformComponents = */ 1024,
/* .MaxGeometryVaryingComponents = */ 64,
/* .MaxTessControlInputComponents = */ 128,
/* .MaxTessControlOutputComponents = */ 128,
/* .MaxTessControlTextureImageUnits = */ 16,
/* .MaxTessControlUniformComponents = */ 1024,
/* .MaxTessControlTotalOutputComponents = */ 4096,
/* .MaxTessEvaluationInputComponents = */ 128,
/* .MaxTessEvaluationOutputComponents = */ 128,
/* .MaxTessEvaluationTextureImageUnits = */ 16,
/* .MaxTessEvaluationUniformComponents = */ 1024,
/* .MaxTessPatchComponents = */ 120,
/* .MaxPatchVertices = */ 32,
/* .MaxTessGenLevel = */ 64,
/* .MaxViewports = */ 16,
/* .MaxVertexAtomicCounters = */ 0,
/* .MaxTessControlAtomicCounters = */ 0,
/* .MaxTessEvaluationAtomicCounters = */ 0,
/* .MaxGeometryAtomicCounters = */ 0,
/* .MaxFragmentAtomicCounters = */ 8,
/* .MaxCombinedAtomicCounters = */ 8,
/* .MaxAtomicCounterBindings = */ 1,
/* .MaxVertexAtomicCounterBuffers = */ 0,
/* .MaxTessControlAtomicCounterBuffers = */ 0,
/* .MaxTessEvaluationAtomicCounterBuffers = */ 0,
/* .MaxGeometryAtomicCounterBuffers = */ 0,
/* .MaxFragmentAtomicCounterBuffers = */ 1,
/* .MaxCombinedAtomicCounterBuffers = */ 1,
/* .MaxAtomicCounterBufferSize = */ 16384,
/* .MaxTransformFeedbackBuffers = */ 4,
/* .MaxTransformFeedbackInterleavedComponents = */ 64,
/* .MaxCullDistances = */ 8,
/* .MaxCombinedClipAndCullDistances = */ 8,
/* .MaxSamples = */ 4,
/* .maxMeshOutputVerticesNV = */ 256,
/* .maxMeshOutputPrimitivesNV = */ 512,
/* .maxMeshWorkGroupSizeX_NV = */ 32,
/* .maxMeshWorkGroupSizeY_NV = */ 1,
/* .maxMeshWorkGroupSizeZ_NV = */ 1,
/* .maxTaskWorkGroupSizeX_NV = */ 32,
/* .maxTaskWorkGroupSizeY_NV = */ 1,
/* .maxTaskWorkGroupSizeZ_NV = */ 1,
/* .maxMeshViewCountNV = */ 4,
/* .maxDualSourceDrawBuffersEXT = */ 4,
/* .limits = */ TLimits{
/* .nonInductiveForLoops = */ true,
/* .whileLoops = */ true,
/* .doWhileLoops = */ true,
/* .generalUniformIndexing = */ true,
/* .generalAttributeMatrixVectorIndexing = */ true,
/* .generalVaryingIndexing = */ true,
/* .generalSamplerIndexing = */ true,
/* .generalVariableIndexing = */ true,
/* .generalConstantMatrixVectorIndexing = */ true,
}};

View file

@ -43,8 +43,6 @@
#include <glslang/Public/ShaderLang.h> #include <glslang/Public/ShaderLang.h>
#include "file.hpp"
// Default include class for normal include convention of search backward // Default include class for normal include convention of search backward
// through the stack of active include paths (for nested includes). // through the stack of active include paths (for nested includes).
// Can be overridden to customize. // Can be overridden to customize.
@ -136,6 +134,7 @@ protected:
// If no path markers, return current working directory. // If no path markers, return current working directory.
// Otherwise, strip file name and return path leading up to it. // Otherwise, strip file name and return path leading up to it.
virtual std::string getDirectory(const std::string) const { virtual std::string getDirectory(const std::string) const {
return file::get_domain_path(file::Domain::Internal).string(); //return file::get_domain_path(file::Domain::Internal).string();
return "";
} }
}; };

View file

@ -0,0 +1,117 @@
#include "shadercompiler.hpp"
#include <spirv_cpp.hpp>
#include <spirv_msl.hpp>
#include <SPIRV/GlslangToSpv.h>
#include "log.hpp"
#include "string_utils.hpp"
#include "includer.hpp"
#include "defaultresources.hpp"
static inline std::string include_path;
ShaderCompiler::ShaderCompiler() {
glslang::InitializeProcess();
}
void ShaderCompiler::set_include_path(const std::string_view path) {
include_path = path;
}
const std::vector<uint32_t> compile_glsl_to_spv(const std::string_view source_string, const EShLanguage shader_language, const CompileOptions& options) {
std::string newString = "#version 430 core\n";
newString += "#extension GL_GOOGLE_include_directive : enable\n";
newString += "#extension GL_GOOGLE_cpp_style_line_directive : enable\n";
for(auto& definition : options.definitions)
newString += "#define " + definition + "\n";
newString += "#line 1\n";
newString += source_string;
const char* InputCString = newString.c_str();
glslang::TShader Shader(shader_language);
Shader.setStrings(&InputCString, 1);
int ClientInputSemanticsVersion = 100; // maps to, say, #define VULKAN 100
glslang::EShTargetClientVersion VulkanClientVersion = glslang::EShTargetVulkan_1_1;
glslang::EShTargetLanguageVersion TargetVersion = glslang::EShTargetSpv_1_0;
Shader.setEnvInput(glslang::EShSourceGlsl, shader_language, glslang::EShClientVulkan, ClientInputSemanticsVersion);
Shader.setEnvClient(glslang::EShClientVulkan, VulkanClientVersion);
Shader.setEnvTarget(glslang::EShTargetSpv, TargetVersion);
TBuiltInResource Resources = DefaultTBuiltInResource;
EShMessages messages = (EShMessages) (EShMsgSpvRules);
DirStackFileIncluder includer;
includer.pushExternalLocalDirectory(include_path);
if (!Shader.parse(&Resources, 100, false, (EShMessages)0, includer)) {
console::error(System::Renderer, "{}", Shader.getInfoLog());
return {};
}
glslang::TProgram Program;
Program.addShader(&Shader);
if(!Program.link(messages)) {
console::error(System::None, "Failed to link shader: {} {} {}", source_string.data(), Shader.getInfoLog(), Shader.getInfoDebugLog());
return {};
}
std::vector<unsigned int> SpirV;
spv::SpvBuildLogger logger;
glslang::SpvOptions spvOptions;
glslang::GlslangToSpv(*Program.getIntermediate(shader_language), SpirV, &logger, &spvOptions);
return SpirV;
}
std::optional<ShaderSource> ShaderCompiler::compile(const ShaderLanguage from_language, const ShaderStage shader_stage, const ShaderSource& shader_source, const ShaderLanguage to_language, const CompileOptions& options) {
if(from_language != ShaderLanguage::GLSL) {
console::error(System::Renderer, "Non-supported input language!");
return std::nullopt;
}
EShLanguage lang = EShLangMiss;
switch(shader_stage) {
case ShaderStage::Vertex:
lang = EShLangVertex;
break;
case ShaderStage::Fragment:
lang = EShLangFragment;
break;
}
auto spirv = compile_glsl_to_spv(shader_source.as_string(), lang, options);
if(spirv.empty()) {
console::error(System::Renderer, "SPIRV generation failed!");
return std::nullopt;
}
#ifdef PLATFORM_MACOS
spirv_cross::CompilerMSL msl(std::move(spirv));
spirv_cross::CompilerMSL::Options opts;
if(options.is_apple_mobile) {
opts.platform = spirv_cross::CompilerMSL::Options::Platform::macOS;
} else {
opts.platform = spirv_cross::CompilerMSL::Options::Platform::iOS;
}
opts.enable_decoration_binding = true;
msl.set_msl_options(opts);
return msl.compile();
#else
return spirv;
#endif
}

View file

@ -8,7 +8,7 @@
#include "gfx.hpp" #include "gfx.hpp"
#include "asset.hpp" #include "asset.hpp"
#include "log.hpp" #include "log.hpp"
#include "shadercompiler.hpp" #include "materialcompiler.hpp"
struct BillPushConstant { struct BillPushConstant {
Matrix4x4 view, mvp; Matrix4x4 view, mvp;

View file

@ -1,7 +1,8 @@
add_executable(ShaderCompiler main.cpp) add_executable(ShaderCompilerTool main.cpp)
target_link_libraries(ShaderCompiler target_link_libraries(ShaderCompilerTool
PUBLIC PRIVATE
${CROSS_LIBS} ShaderCompiler
Log) Log
set_engine_properties(ShaderCompiler) Utility)
set_output_dir(ShaderCompiler) set_engine_properties(ShaderCompilerTool)
set_output_dir(ShaderCompilerTool)

View file

@ -1,236 +1,86 @@
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <spirv_cpp.hpp> #include "shadercompiler.hpp"
#include <spirv_msl.hpp>
#include <glslang/Public/ShaderLang.h>
#include <SPIRV/GlslangToSpv.h>
#include "DirStackIncluder.h"
#include "log.hpp" #include "log.hpp"
#include "string_utils.hpp"
const TBuiltInResource DefaultTBuiltInResource = { bool has_extension(const std::filesystem::path path, const std::string_view extension) {
/* .MaxLights = */ 32, return string_contains(path.filename().string(), extension);
/* .MaxClipPlanes = */ 6,
/* .MaxTextureUnits = */ 32,
/* .MaxTextureCoords = */ 32,
/* .MaxVertexAttribs = */ 64,
/* .MaxVertexUniformComponents = */ 4096,
/* .MaxVaryingFloats = */ 64,
/* .MaxVertexTextureImageUnits = */ 32,
/* .MaxCombinedTextureImageUnits = */ 80,
/* .MaxTextureImageUnits = */ 32,
/* .MaxFragmentUniformComponents = */ 4096,
/* .MaxDrawBuffers = */ 32,
/* .MaxVertexUniformVectors = */ 128,
/* .MaxVaryingVectors = */ 8,
/* .MaxFragmentUniformVectors = */ 16,
/* .MaxVertexOutputVectors = */ 16,
/* .MaxFragmentInputVectors = */ 15,
/* .MinProgramTexelOffset = */ -8,
/* .MaxProgramTexelOffset = */ 7,
/* .MaxClipDistances = */ 8,
/* .MaxComputeWorkGroupCountX = */ 65535,
/* .MaxComputeWorkGroupCountY = */ 65535,
/* .MaxComputeWorkGroupCountZ = */ 65535,
/* .MaxComputeWorkGroupSizeX = */ 1024,
/* .MaxComputeWorkGroupSizeY = */ 1024,
/* .MaxComputeWorkGroupSizeZ = */ 64,
/* .MaxComputeUniformComponents = */ 1024,
/* .MaxComputeTextureImageUnits = */ 16,
/* .MaxComputeImageUniforms = */ 8,
/* .MaxComputeAtomicCounters = */ 8,
/* .MaxComputeAtomicCounterBuffers = */ 1,
/* .MaxVaryingComponents = */ 60,
/* .MaxVertexOutputComponents = */ 64,
/* .MaxGeometryInputComponents = */ 64,
/* .MaxGeometryOutputComponents = */ 128,
/* .MaxFragmentInputComponents = */ 128,
/* .MaxImageUnits = */ 8,
/* .MaxCombinedImageUnitsAndFragmentOutputs = */ 8,
/* .MaxCombinedShaderOutputResources = */ 8,
/* .MaxImageSamples = */ 0,
/* .MaxVertexImageUniforms = */ 0,
/* .MaxTessControlImageUniforms = */ 0,
/* .MaxTessEvaluationImageUniforms = */ 0,
/* .MaxGeometryImageUniforms = */ 0,
/* .MaxFragmentImageUniforms = */ 8,
/* .MaxCombinedImageUniforms = */ 8,
/* .MaxGeometryTextureImageUnits = */ 16,
/* .MaxGeometryOutputVertices = */ 256,
/* .MaxGeometryTotalOutputComponents = */ 1024,
/* .MaxGeometryUniformComponents = */ 1024,
/* .MaxGeometryVaryingComponents = */ 64,
/* .MaxTessControlInputComponents = */ 128,
/* .MaxTessControlOutputComponents = */ 128,
/* .MaxTessControlTextureImageUnits = */ 16,
/* .MaxTessControlUniformComponents = */ 1024,
/* .MaxTessControlTotalOutputComponents = */ 4096,
/* .MaxTessEvaluationInputComponents = */ 128,
/* .MaxTessEvaluationOutputComponents = */ 128,
/* .MaxTessEvaluationTextureImageUnits = */ 16,
/* .MaxTessEvaluationUniformComponents = */ 1024,
/* .MaxTessPatchComponents = */ 120,
/* .MaxPatchVertices = */ 32,
/* .MaxTessGenLevel = */ 64,
/* .MaxViewports = */ 16,
/* .MaxVertexAtomicCounters = */ 0,
/* .MaxTessControlAtomicCounters = */ 0,
/* .MaxTessEvaluationAtomicCounters = */ 0,
/* .MaxGeometryAtomicCounters = */ 0,
/* .MaxFragmentAtomicCounters = */ 8,
/* .MaxCombinedAtomicCounters = */ 8,
/* .MaxAtomicCounterBindings = */ 1,
/* .MaxVertexAtomicCounterBuffers = */ 0,
/* .MaxTessControlAtomicCounterBuffers = */ 0,
/* .MaxTessEvaluationAtomicCounterBuffers = */ 0,
/* .MaxGeometryAtomicCounterBuffers = */ 0,
/* .MaxFragmentAtomicCounterBuffers = */ 1,
/* .MaxCombinedAtomicCounterBuffers = */ 1,
/* .MaxAtomicCounterBufferSize = */ 16384,
/* .MaxTransformFeedbackBuffers = */ 4,
/* .MaxTransformFeedbackInterleavedComponents = */ 64,
/* .MaxCullDistances = */ 8,
/* .MaxCombinedClipAndCullDistances = */ 8,
/* .MaxSamples = */ 4,
/* .maxMeshOutputVerticesNV = */ 256,
/* .maxMeshOutputPrimitivesNV = */ 512,
/* .maxMeshWorkGroupSizeX_NV = */ 32,
/* .maxMeshWorkGroupSizeY_NV = */ 1,
/* .maxMeshWorkGroupSizeZ_NV = */ 1,
/* .maxTaskWorkGroupSizeX_NV = */ 32,
/* .maxTaskWorkGroupSizeY_NV = */ 1,
/* .maxTaskWorkGroupSizeZ_NV = */ 1,
/* .maxMeshViewCountNV = */ 4,
/* .maxDualSourceDrawBuffersEXT = */ 4,
/* .limits = */ TLimits{
/* .nonInductiveForLoops = */ true,
/* .whileLoops = */ true,
/* .doWhileLoops = */ true,
/* .generalUniformIndexing = */ true,
/* .generalAttributeMatrixVectorIndexing = */ true,
/* .generalVaryingIndexing = */ true,
/* .generalSamplerIndexing = */ true,
/* .generalVariableIndexing = */ true,
/* .generalConstantMatrixVectorIndexing = */ true,
}};
const std::vector<unsigned int> CompileGLSL(const std::string& filename, EShLanguage ShaderType) {
std::string newString = "#version 430 core\n";
newString += "#extension GL_GOOGLE_include_directive : enable\n";
newString += "#extension GL_GOOGLE_cpp_style_line_directive : enable\n";
newString += "#line 1\n";
newString += filename;
const char* InputCString = newString.c_str();
glslang::TShader Shader(ShaderType);
Shader.setStrings(&InputCString, 1);
int ClientInputSemanticsVersion = 100; // maps to, say, #define VULKAN 100
glslang::EShTargetClientVersion VulkanClientVersion = glslang::EShTargetVulkan_1_1;
glslang::EShTargetLanguageVersion TargetVersion = glslang::EShTargetSpv_1_0;
Shader.setEnvInput(glslang::EShSourceGlsl, ShaderType, glslang::EShClientVulkan, ClientInputSemanticsVersion);
Shader.setEnvClient(glslang::EShClientVulkan, VulkanClientVersion);
Shader.setEnvTarget(glslang::EShTargetSpv, TargetVersion);
TBuiltInResource Resources = DefaultTBuiltInResource;
EShMessages messages = (EShMessages) (EShMsgSpvRules);
DirStackFileIncluder includer;
if (!Shader.parse(&Resources, 100, false, messages, includer)) {
console::error(System::None, "Failed to parse shader: {} {} {}", filename, Shader.getInfoLog(), Shader.getInfoDebugLog());
return {};
}
glslang::TProgram Program;
Program.addShader(&Shader);
if(!Program.link(messages)) {
console::error(System::None, "Failed to link shader: {} {} {}", filename, Shader.getInfoLog(), Shader.getInfoDebugLog());
return {};
}
std::vector<unsigned int> SpirV;
spv::SpvBuildLogger logger;
glslang::SpvOptions spvOptions;
glslang::GlslangToSpv(*Program.getIntermediate(ShaderType), SpirV, &logger, &spvOptions);
return SpirV;
} }
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
if(argc < 2) if(argc < 2) {
console::error(System::Core, "Not enough arguments!");
return -1; return -1;
}
glslang::InitializeProcess(); shader_compiler.set_include_path(std::filesystem::current_path().string());
std::ifstream t(argv[1]); std::filesystem::path source_path = argv[1];
std::filesystem::path destination_path = argv[2];
std::ifstream t(source_path);
std::stringstream buffer; std::stringstream buffer;
buffer << t.rdbuf(); buffer << t.rdbuf();
if(std::string(argv[1]).find("nocompile") != std::string::npos) { if(has_extension(source_path, "nocompile")) {
std::string outname = argv[2]; std::string outname = argv[2];
outname = outname.substr(0, outname.length() - 5); // remove .glsl outname = outname.substr(0, outname.length() - 5); // remove .glsl
outname = outname.substr(0, outname.length() - 10); // remove .nocompile outname = outname.substr(0, outname.length() - 10); // remove .nocompile
std::ofstream out(outname + ".glsl"); std::ofstream out(outname + ".glsl");
out << buffer.rdbuf(); out << buffer.rdbuf();
console::info(System::Core, "Successfully written {} to {}.", source_path, destination_path);
return 0;
} else { } else {
EShLanguage lang; ShaderStage stage;
if (std::string(argv[1]).find("vert") != std::string::npos) { if(has_extension(source_path, ".vert"))
lang = EShLangVertex; stage = ShaderStage::Vertex;
} else
else { stage = ShaderStage::Fragment;
lang = EShLangFragment;
ShaderLanguage language;
CompileOptions options;
#ifdef PLATFORM_MACOS
options.is_apple_mobile = (bool)argv[3];
language = ShaderLanguage::MSL;
destination_path.replace_extension(".msl");
#else
language = ShaderLanguage::SPIRV;
destination_path.replace_extension(".spv");
#endif
const auto compiled_source = shader_compiler.compile(ShaderLanguage::GLSL, stage, buffer.str(), language, options);
if(!compiled_source.has_value()) {
console::error(System::Core, "Error when compiling {}!", source_path);
return -1;
} }
std::string outname = argv[2]; switch(language) {
outname = outname.substr(0, outname.length() - 5); case ShaderLanguage::SPIRV:
{
const auto spirv = compiled_source->as_bytecode();
// generate msl std::ofstream out(destination_path, std::ios::binary); // remove .glsl
#ifdef PLATFORM_MACOS out.write((char*)spirv.data(), spirv.size() * sizeof(uint32_t));
bool is_ios = (bool)argv[3]; }
break;
{ case ShaderLanguage::MSL:
auto vertexSPIRV = CompileGLSL(buffer.str(), lang); {
if (vertexSPIRV.size() == 0) std::ofstream out(destination_path); // remove .glsl
return -1; out << compiled_source->as_string();
}
spirv_cross::CompilerMSL msl(std::move(vertexSPIRV)); break;
default:
spirv_cross::CompilerMSL::Options opts; break;
opts.platform = is_ios ? spirv_cross::CompilerMSL::Options::Platform::iOS : spirv_cross::CompilerMSL::Options::Platform::macOS;
opts.enable_decoration_binding = true;
msl.set_msl_options(opts);
std::string s = msl.compile();
std::ofstream out(outname + ".msl"); // remove .glsl
out << s;
} }
#else
// generate spv
{
auto vertexSPIRV = CompileGLSL(buffer.str(), lang);
if (vertexSPIRV.size() == 0)
return -1;
std::ofstream out(outname + ".spv", std::ios::binary); // remove .glsl console::info(System::Core, "Successfully written shader from {} to {}.", source_path, destination_path);
out.write((char*)vertexSPIRV.data(), vertexSPIRV.size() * sizeof(uint32_t));
} return 0;
#endif
} }
return 0;
} }