diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fceca7fb..e134248c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - New `BinaryOutputArchive` / `BinaryInputArchive` pair (`yup_core/serialisation/yup_BinaryArchive.h`): binary stream archives that plug into the `SerialisationTraits` system; used internally by `ShaderBundle` to serialise `ShaderReflection` data into `REFL` RIFF chunks. - `SerialisationTraits` specialisations for all `ShaderReflection` nested types (`EntryPoint`, `WorkgroupSize`, `ResourceMember`, `ResourceBinding`, `BuiltInBinding`, `SpecializationConstant`, `ShaderReflection`) enabling binary and JSON serialisation of full reflection data. Both `ShaderBundle` and `ShaderBundleCompiler` are compiled only when `YUP_ENABLE_SHADER_TRANSPILER` is `1`. - `TranspileOptions` now exposes `spirvOptimize` flag; wired through to `glslang::SpvOptions` (disabled by default; requires SPIRV-Tools linked into glslang to take effect). +- New standalone `yup_shader_bundler` console tool (`cmake/tools/shader_bundler`): takes a `.vert` and `.frag` GLSL pair on disk and produces a single `.ysl` bundle containing transpiled variants for all target languages (GLSL/ESSL/HLSL/MSL/WGSL). +- New `yup_add_shader_bundle()` CMake helper (`cmake/yup_shader_bundler.cmake`): builds the `yup_shader_bundler` tool for the host once (cached in the global property `YUP_SHADER_BUNDLER_EXECUTABLE`), runs it at configure time to generate the `.ysl`, and embeds it into a linkable object library via `yup_add_embedded_binary_resources`. Works even when the outer build is cross-compiling, since the tool is built in its own host binary tree without forwarding the cross toolchain. #### macOS diff --git a/cmake/tools/shader_bundler/CMakeLists.txt b/cmake/tools/shader_bundler/CMakeLists.txt new file mode 100644 index 000000000..89ea507ca --- /dev/null +++ b/cmake/tools/shader_bundler/CMakeLists.txt @@ -0,0 +1,62 @@ +# ============================================================================== +# +# This file is part of the YUP library. +# Copyright (c) 2026 - kunitoki@gmail.com +# +# YUP is an open source library subject to open-source licensing. +# +# The code included in this file is provided under the terms of the ISC license +# http://www.isc.org/downloads/software-support-policy/isc-license. Permission +# To use, copy, modify, and/or distribute this software for any purpose with or +# without fee is hereby granted provided that the above copyright notice and +# this permission notice appear in all copies. +# +# YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER +# EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE +# DISCLAIMED. +# +# ============================================================================== + +cmake_minimum_required (VERSION 3.31) + +# ==== Prepare target +set (target_name yup_shader_bundler) +set (target_version "2.0.0") + +project (${target_name} VERSION ${target_version}) + +# ==== Bootstrap YUP modules (this is always built as a standalone host tool) +get_filename_component (yup_root "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) +include (${yup_root}/cmake/yup.cmake) + +# Only add the modules actually needed by the shader bundler tool. +# Adding all default modules would pull in audio modules that require host +# system packages (e.g. alsa, pulseaudio) which may not be available. +yup_add_module ("${yup_root}/thirdparty/zlib" "" "Thirdparty") +yup_add_module ("${yup_root}/thirdparty/glslang" "" "Thirdparty") +yup_add_module ("${yup_root}/thirdparty/spirv_cross" "" "Thirdparty") +yup_add_module ("${yup_root}/thirdparty/spirv_tools" "" "Thirdparty") +yup_add_module ("${yup_root}/modules/yup_core" "" "Modules") +yup_add_module ("${yup_root}/modules/yup_shading" "" "Modules") +add_library (yup::yup_core ALIAS yup_core) +add_library (yup::yup_shading ALIAS yup_shading) + +# ==== Prepare target +yup_standalone_app ( + TARGET_NAME ${target_name} + TARGET_VERSION ${target_version} + TARGET_IDE_GROUP "Tools" + TARGET_APP_ID "org.yup.${target_name}" + TARGET_APP_NAMESPACE "org.yup" + TARGET_CONSOLE ON + MODULES + yup::yup_core + yup::yup_shading + glslang + spirv_cross + spirv_tools) + +# ==== Prepare sources +file (GLOB_RECURSE sources "${CMAKE_CURRENT_LIST_DIR}/source/*.cpp") +source_group (TREE ${CMAKE_CURRENT_LIST_DIR}/ FILES ${sources}) +target_sources (${target_name} PRIVATE ${sources}) diff --git a/cmake/tools/shader_bundler/source/main.cpp b/cmake/tools/shader_bundler/source/main.cpp new file mode 100644 index 000000000..183fe6fb9 --- /dev/null +++ b/cmake/tools/shader_bundler/source/main.cpp @@ -0,0 +1,193 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2026 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include +#include + +using namespace yup; + +static String getOptionValue (const ArgumentList& args, StringRef option, const String& defaultValue = {}) +{ + const int index = args.indexOfOption (option); + if (index < 0) + return defaultValue; + + // Long option assignment form: "--opt=value" + const String assigned = args.getValueForOption (option); + if (assigned.isNotEmpty()) + return assigned; + + // Space-separated form: "--opt value" + if (index + 1 < args.size() && ! args[index + 1].isOption()) + return args[index + 1].text; + + return defaultValue; +} + +static void printUsage() +{ + Logger::getCurrentLogger()->writeToLog ( + "Usage: yup_shader_bundler [options]\n" + "\n" + "Options:\n" + " --vert Path to vertex shader (.vert) file\n" + " --frag Path to fragment shader (.frag) file\n" + " --output Output .ysl bundle file path\n" + " --entry Shader entry point name (default: main)\n" + " --glsl-version GLSL version to target (default: 450)\n" + " --help|-h Print this help\n" + "\n" + "Compiles a vertex and fragment GLSL shader pair into a portable\n" + ".ysl bundle containing transpiled variants for all target languages\n" + "(GLSL, ESSL, HLSL, MSL).\n"); +} + +int main (int argc, char* argv[]) +{ + ArgumentList args (argc, argv); + + if (args.containsOption ("--help|-h")) + { + printUsage(); + return 0; + } + + args.failIfOptionIsMissing ("--vert"); + args.failIfOptionIsMissing ("--frag"); + args.failIfOptionIsMissing ("--output"); + + const auto resolveFile = [] (const String& path) -> File + { + if (path.isEmpty()) + return {}; + if (File::isAbsolutePath (path)) + return File (path); + return File::getCurrentWorkingDirectory().getChildFile (path); + }; + + const auto vertPath = resolveFile (getOptionValue (args, "--vert")); + const auto fragPath = resolveFile (getOptionValue (args, "--frag")); + const auto outputPath = resolveFile (getOptionValue (args, "--output")); + + const auto entryPoint = getOptionValue (args, "--entry", "main"); + + const String glslVersionVal = getOptionValue (args, "--glsl-version", "450"); + const int glslVersion = glslVersionVal.getIntValue(); + + // -- Validate input files + if (! vertPath.existsAsFile()) + { + const String msg = "Vertex shader file not found: " + vertPath.getFullPathName(); + Logger::getCurrentLogger()->writeToLog (msg); + return 1; + } + + if (! fragPath.existsAsFile()) + { + const String msg = "Fragment shader file not found: " + fragPath.getFullPathName(); + Logger::getCurrentLogger()->writeToLog (msg); + return 1; + } + + const String vertSource = vertPath.loadFileAsString(); + const String fragSource = fragPath.loadFileAsString(); + + if (vertSource.isEmpty()) + { + const String msg = "Vertex shader file is empty: " + vertPath.getFullPathName(); + Logger::getCurrentLogger()->writeToLog (msg); + return 1; + } + + if (fragSource.isEmpty()) + { + const String msg = "Fragment shader file is empty: " + fragPath.getFullPathName(); + Logger::getCurrentLogger()->writeToLog (msg); + return 1; + } + + // -- Target all supported shading languages + const std::vector targetLanguages = { + ShaderLanguage::glsl, + ShaderLanguage::essl, + ShaderLanguage::hlsl, + ShaderLanguage::msl + }; + + auto makeEntry = [&] (ShaderStage stage) + { + ShaderBundleEntry entry; + entry.stage = stage; + entry.targetLanguages = targetLanguages; + entry.options.entryPoint = entryPoint; + entry.options.glslVersion = glslVersion; + return entry; + }; + + ShaderBundleCompiler compiler; + + // Compile vertex stage + ShaderBundleCompileRequest vertRequest; + vertRequest.source = vertSource; + vertRequest.sourceLanguage = ShaderLanguage::glsl; + vertRequest.entries.push_back (makeEntry (ShaderStage::vertex)); + + auto vsBundle = compiler.compile (vertRequest); + if (vsBundle.failed()) + { + const String msg = "Vertex shader compilation failed: " + vsBundle.getErrorMessage(); + Logger::getCurrentLogger()->writeToLog (msg); + return 1; + } + + // Compile fragment stage + ShaderBundleCompileRequest fragRequest; + fragRequest.source = fragSource; + fragRequest.sourceLanguage = ShaderLanguage::glsl; + fragRequest.entries.push_back (makeEntry (ShaderStage::fragment)); + + auto fsBundle = compiler.compile (fragRequest); + if (fsBundle.failed()) + { + const String msg = "Fragment shader compilation failed: " + fsBundle.getErrorMessage(); + Logger::getCurrentLogger()->writeToLog (msg); + return 1; + } + + // Merge both stages into a single bundle + ShaderBundle bundle; + for (const auto& info : vsBundle.getReference().getShaders()) + bundle.addShader (info); + for (const auto& info : fsBundle.getReference().getShaders()) + bundle.addShader (info); + + // Persist to file + const auto saveResult = bundle.saveToFile (outputPath); + if (saveResult.failed()) + { + const String msg = "Failed to save bundle: " + saveResult.getErrorMessage(); + Logger::getCurrentLogger()->writeToLog (msg); + return 1; + } + + Logger::getCurrentLogger()->writeToLog ("Shader bundle written to: " + outputPath.getFullPathName()); + return 0; +} diff --git a/cmake/yup.cmake b/cmake/yup.cmake index b01088de3..af2c9f01d 100644 --- a/cmake/yup.cmake +++ b/cmake/yup.cmake @@ -108,6 +108,7 @@ include (${CMAKE_CURRENT_LIST_DIR}/yup_standalone.cmake) include (${CMAKE_CURRENT_LIST_DIR}/yup_pluginval.cmake) include (${CMAKE_CURRENT_LIST_DIR}/yup_audio_plugin.cmake) include (${CMAKE_CURRENT_LIST_DIR}/yup_embed_binary.cmake) +include (${CMAKE_CURRENT_LIST_DIR}/yup_shader_bundler.cmake) include (${CMAKE_CURRENT_LIST_DIR}/yup_python.cmake) # Platform specific includes diff --git a/cmake/yup_shader_bundler.cmake b/cmake/yup_shader_bundler.cmake new file mode 100644 index 000000000..af741bdac --- /dev/null +++ b/cmake/yup_shader_bundler.cmake @@ -0,0 +1,154 @@ +# ============================================================================== +# +# This file is part of the YUP library. +# Copyright (c) 2026 - kunitoki@gmail.com +# +# YUP is an open source library subject to open-source licensing. +# +# The code included in this file is provided under the terms of the ISC license +# http://www.isc.org/downloads/software-support-policy/isc-license. Permission +# To use, copy, modify, and/or distribute this software for any purpose with or +# without fee is hereby granted provided that the above copyright notice and +# this permission notice appear in all copies. +# +# YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER +# EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE +# DISCLAIMED. +# +# ============================================================================== + +#============================================================================== +# Builds the yup_shader_bundler console tool for the host once and caches the +# resulting executable path in the global property YUP_SHADER_BUNDLER_EXECUTABLE. +# +# The tool is built in its own binary tree using the host compiler (no toolchain +# file is forwarded), so it is runnable at configure time even when the outer +# build is cross-compiling (Android, iOS, WebAssembly). + +function (_yup_build_shader_bundler_tool output_variable) + get_property (cached_exe GLOBAL PROPERTY YUP_SHADER_BUNDLER_EXECUTABLE) + if (cached_exe AND EXISTS "${cached_exe}") + set (${output_variable} "${cached_exe}" PARENT_SCOPE) + return() + endif() + + get_filename_component (tool_source_dir "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/tools/shader_bundler" ABSOLUTE) + set (tool_build_dir "${CMAKE_BINARY_DIR}/_host_tools/shader_bundler") + + # ==== Configure the host build (no toolchain file -> host compiler) + _yup_message (STATUS "Configuring host shader bundler tool") + _yup_message (STATUS " * tool_source_dir: ${tool_source_dir}") + _yup_message (STATUS " * tool_build_dir: ${tool_build_dir}") + _yup_execute_process_or_fail ( + "${CMAKE_COMMAND}" + -S "${tool_source_dir}" + -B "${tool_build_dir}" + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_TOOLCHAIN_FILE=) + + # ==== Build it + _yup_message (STATUS "Building host shader bundler tool") + _yup_execute_process_or_fail ( + "${CMAKE_COMMAND}" + --build "${tool_build_dir}" + --config Release + --parallel 4) + + # ==== Locate the produced executable + set (exe_name "yup_shader_bundler") + if (CMAKE_HOST_WIN32) + set (exe_name "yup_shader_bundler.exe") + endif() + + file (GLOB_RECURSE candidate_exes "${tool_build_dir}/${exe_name}") + list (FILTER candidate_exes EXCLUDE REGEX "\\.dSYM/") + list (LENGTH candidate_exes num_candidates) + + if (num_candidates EQUAL 0) + message (FATAL_ERROR "Failed to locate built shader bundler tool in ${tool_build_dir}") + endif() + + list (GET candidate_exes 0 tool_exe) + + set_property (GLOBAL PROPERTY YUP_SHADER_BUNDLER_EXECUTABLE "${tool_exe}") + _yup_message (STATUS " * shader bundler executable: ${tool_exe}") + + set (${output_variable} "${tool_exe}" PARENT_SCOPE) +endfunction() + +#============================================================================== +# Compiles a vertex/fragment GLSL shader pair into a .ysl bundle (at configure +# time) and embeds it into an OBJECT library that can be linked into a target. +# +# Usage: +# yup_add_shader_bundle ( +# VERT +# FRAG +# [OUTPUT_NAME ] # default: +# [RESOURCE_NAME ] # default: +# [NAMESPACE ] # default: yup +# [ENTRY ] # default: main +# [GLSL_VERSION ]) # default: 450 +# +# After the call, link against and include the generated header +# ".h", which exposes: +# extern const uint8_t _data[]; +# extern const size_t _size; +# The bytes can be loaded at runtime with ShaderBundle::loadFromData(). + +function (yup_add_shader_bundle library_name) + set (options "") + set (one_value_args VERT FRAG OUTPUT_NAME RESOURCE_NAME NAMESPACE ENTRY GLSL_VERSION) + set (multi_value_args "") + + cmake_parse_arguments (YUP_ARG "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN}) + + if (NOT YUP_ARG_VERT) + message (FATAL_ERROR "yup_add_shader_bundle: VERT argument is required") + endif() + if (NOT YUP_ARG_FRAG) + message (FATAL_ERROR "yup_add_shader_bundle: FRAG argument is required") + endif() + + _yup_set_default (YUP_ARG_OUTPUT_NAME "${library_name}") + _yup_set_default (YUP_ARG_RESOURCE_NAME "${library_name}") + _yup_set_default (YUP_ARG_NAMESPACE "yup") + _yup_set_default (YUP_ARG_ENTRY "main") + _yup_set_default (YUP_ARG_GLSL_VERSION "450") + + get_filename_component (vert_path "${YUP_ARG_VERT}" ABSOLUTE) + get_filename_component (frag_path "${YUP_ARG_FRAG}" ABSOLUTE) + + if (NOT EXISTS "${vert_path}") + message (FATAL_ERROR "yup_add_shader_bundle: vertex shader not found: ${vert_path}") + endif() + if (NOT EXISTS "${frag_path}") + message (FATAL_ERROR "yup_add_shader_bundle: fragment shader not found: ${frag_path}") + endif() + + # ==== Ensure the host tool is available (built and cached once) + _yup_build_shader_bundler_tool (shader_bundler_exe) + + # ==== Generate the .ysl bundle at configure time + set (bundle_path "${CMAKE_CURRENT_BINARY_DIR}/${YUP_ARG_OUTPUT_NAME}.ysl") + + _yup_message (STATUS "Generating shader bundle ${bundle_path}") + _yup_execute_process_or_fail ( + "${shader_bundler_exe}" + --vert "${vert_path}" + --frag "${frag_path}" + --output "${bundle_path}" + --entry "${YUP_ARG_ENTRY}" + --glsl-version "${YUP_ARG_GLSL_VERSION}") + + # ==== Embed the generated bundle into an object library + yup_add_embedded_binary_resources ( + ${library_name} + OUT_DIR ShaderBundles + HEADER "${YUP_ARG_OUTPUT_NAME}.h" + NAMESPACE ${YUP_ARG_NAMESPACE} + RESOURCE_NAMES + ${YUP_ARG_RESOURCE_NAME} + RESOURCES + "${bundle_path}") +endfunction() diff --git a/examples/graphics/CMakeLists.txt b/examples/graphics/CMakeLists.txt index 48777f456..9fee0a016 100644 --- a/examples/graphics/CMakeLists.txt +++ b/examples/graphics/CMakeLists.txt @@ -49,6 +49,18 @@ if (ANDROID) set (link_libraries "${target_name}_binary_data") endif() +# ==== Embed shaders +yup_add_shader_bundle ( + "${target_name}_binary_shaders" + VERT ${CMAKE_CURRENT_LIST_DIR}/data/shaders/cube.vert + FRAG ${CMAKE_CURRENT_LIST_DIR}/data/shaders/cube.frag + NAMESPACE yup + OUTPUT_NAME + ShaderBundle + RESOURCE_NAME + ShaderBundleFile) +list (APPEND link_libraries "${target_name}_binary_shaders") + # ==== Prepare target set (additional_modules "") set (additional_definitions "") @@ -56,7 +68,6 @@ if (YUP_PLATFORM_DESKTOP AND NOT YUP_PLATFORM_WINDOWS) set (additional_modules yup::yup_python) endif() - yup_standalone_app ( TARGET_NAME ${target_name} TARGET_VERSION ${target_version} diff --git a/examples/graphics/data/shaders/cube.frag b/examples/graphics/data/shaders/cube.frag new file mode 100644 index 000000000..657339e76 --- /dev/null +++ b/examples/graphics/data/shaders/cube.frag @@ -0,0 +1,16 @@ +#version 450 + +layout(location = 0) in vec3 v_color; +layout(location = 1) in vec3 v_normal; +layout(location = 2) in vec2 v_uv; +layout(set = 0, binding = 1) uniform texture2D u_tex; +layout(set = 0, binding = 2) uniform sampler u_samp; +layout(location = 0) out vec4 fragColor; + +void main() { + vec3 light = normalize(vec3(0.503, 0.671, -0.419)); + float ndotl = clamp(dot(normalize(v_normal), light), 0.0, 1.0); + vec4 tex = texture(sampler2D(u_tex, u_samp), vec2(1.0 - v_uv.x, v_uv.y)); + vec3 base = mix(v_color, tex.rgb, tex.a); + fragColor = vec4(base * (0.35 + 0.65 * ndotl), 1.0); +} \ No newline at end of file diff --git a/examples/graphics/data/shaders/cube.vert b/examples/graphics/data/shaders/cube.vert new file mode 100644 index 000000000..0b081adff --- /dev/null +++ b/examples/graphics/data/shaders/cube.vert @@ -0,0 +1,29 @@ +#version 450 + +layout(location = 0) in vec3 a_pos; +layout(location = 1) in vec3 a_color; +layout(location = 2) in vec3 a_normal; +layout(location = 3) in vec2 a_uv; +layout(set = 0, binding = 0) uniform CubeUniforms { + float angleY; float angleX; float aspect; float pad; +} u; +layout(location = 0) out vec3 v_color; +layout(location = 1) out vec3 v_normal; +layout(location = 2) out vec2 v_uv; + +void main() { + float cy = cos(u.angleY), sy = sin(u.angleY); + float cx = cos(u.angleX), sx = sin(u.angleX); + vec3 p = a_pos; + vec3 ry = vec3(p.x*cy + p.z*sy, p.y, -p.x*sy + p.z*cy); + vec3 rx = vec3(ry.x, ry.y*cx - ry.z*sx, ry.y*sx + ry.z*cx); + vec3 n = a_normal; + vec3 ryn = vec3(n.x*cy + n.z*sy, n.y, -n.x*sy + n.z*cy); + vec3 rxn = vec3(ryn.x, ryn.y*cx - ryn.z*sx, ryn.y*sx + ryn.z*cx); + float d = rx.z + 3.5; + float fov = 1.7320508; + gl_Position = vec4(rx.x * fov / u.aspect, rx.y * fov, (d - 0.1) / 99.9 * d, d); + v_color = a_color; + v_normal = rxn; + v_uv = a_uv; +} \ No newline at end of file diff --git a/examples/graphics/source/examples/SpinningCubeDemo.h b/examples/graphics/source/examples/SpinningCubeDemo.h index 968246913..9189dcefd 100644 --- a/examples/graphics/source/examples/SpinningCubeDemo.h +++ b/examples/graphics/source/examples/SpinningCubeDemo.h @@ -24,6 +24,8 @@ #include #include +#include + //============================================================================== /** @@ -617,10 +619,24 @@ void main() { void initCube() { - auto result = yup::GpuPipeline::compileFromGlsl (*capturedContext, - currentVertSource, - currentFragSource, - cubePipelineOptions()); + auto loaded = yup::ShaderBundle::loadFromData (yup::ShaderBundleFile_data, yup::ShaderBundleFile_size); + if (loaded.failed()) + { + yup::Logger::outputDebugString ("SpinningCubeDemo: failed to load shader bundle: " + loaded.getErrorMessage()); + return; + } + + auto result = yup::GpuPipeline::compileFromBundle (*capturedContext, + loaded.getReference(), + cubePipelineOptions()); + + const auto& bundle = loaded.getReference(); + + if (auto* vs = bundle.findShader (yup::ShaderStage::vertex, yup::ShaderLanguage::glsl)) + currentVertSource = vs->source; + + if (auto* fs = bundle.findShader (yup::ShaderStage::fragment, yup::ShaderLanguage::glsl)) + currentFragSource = fs->source; if (result.failed()) { diff --git a/modules/yup_core/yup_core.cpp b/modules/yup_core/yup_core.cpp index d955f37ba..6b95d2932 100644 --- a/modules/yup_core/yup_core.cpp +++ b/modules/yup_core/yup_core.cpp @@ -120,6 +120,9 @@ YUP_END_IGNORE_WARNINGS_MSVC #endif #if YUP_USE_CURL +#if ! __has_include() +#error "YUP_USE_CURL is explicitly enabled but is not available" +#endif #include #endif #endif diff --git a/modules/yup_core/yup_core.h b/modules/yup_core/yup_core.h index 0465d7593..5687f64cf 100644 --- a/modules/yup_core/yup_core.h +++ b/modules/yup_core/yup_core.h @@ -144,7 +144,11 @@ If you disable this then https/ssl support will not be available on Linux. */ #ifndef YUP_USE_CURL +#if __has_include() #define YUP_USE_CURL 1 +#else +#define YUP_USE_CURL 0 +#endif #endif /** Config: YUP_LOAD_CURL_SYMBOLS_LAZILY