Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
62 changes: 62 additions & 0 deletions cmake/tools/shader_bundler/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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})
193 changes: 193 additions & 0 deletions cmake/tools/shader_bundler/source/main.cpp
Original file line number Diff line number Diff line change
@@ -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 <yup_core/yup_core.h>
#include <yup_shading/yup_shading.h>

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> Path to vertex shader (.vert) file\n"
" --frag <path> Path to fragment shader (.frag) file\n"
" --output <path> Output .ysl bundle file path\n"
" --entry <name> Shader entry point name (default: main)\n"
" --glsl-version <n> 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<ShaderLanguage> 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;
}
1 change: 1 addition & 0 deletions cmake/yup.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading