init
This commit is contained in:
9
.gitmodules
vendored
Normal file
9
.gitmodules
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[submodule "third_party/json"]
|
||||||
|
path = third_party/json
|
||||||
|
url = https://github.com/nlohmann/json.git
|
||||||
|
[submodule "third_party/spdlog"]
|
||||||
|
path = third_party/spdlog
|
||||||
|
url = https://github.com/gabime/spdlog.git
|
||||||
|
[submodule "third_party/glslang"]
|
||||||
|
path = third_party/glslang
|
||||||
|
url = https://github.com/KhronosGroup/glslang.git
|
||||||
53
CMakeLists.txt
Normal file
53
CMakeLists.txt
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
add_executable(TheChef
|
||||||
|
main.cpp
|
||||||
|
"ShaderCompiler.h" "ShaderCompiler.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_features(TheChef PRIVATE cxx_std_17)
|
||||||
|
|
||||||
|
# Good practice for tools
|
||||||
|
target_compile_definitions(TheChef PRIVATE
|
||||||
|
_CRT_SECURE_NO_WARNINGS
|
||||||
|
)
|
||||||
|
if (NOT TARGET nlohmann_json::nlohmann_json)
|
||||||
|
add_subdirectory(third_party/json)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT TARGET spdlog::spdlog)
|
||||||
|
add_subdirectory(third_party/spdlog)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
||||||
|
|
||||||
|
set(GLSLANG_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/glslang")
|
||||||
|
set(SCRIPT "${GLSLANG_DIR}/update_glslang_sources.py")
|
||||||
|
|
||||||
|
execute_process(
|
||||||
|
COMMAND "${Python3_EXECUTABLE}" "${SCRIPT}"
|
||||||
|
WORKING_DIRECTORY "${GLSLANG_DIR}"
|
||||||
|
RESULT_VARIABLE rc
|
||||||
|
OUTPUT_VARIABLE out
|
||||||
|
ERROR_VARIABLE err
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
ERROR_STRIP_TRAILING_WHITESPACE
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT rc EQUAL 0)
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"update_glslang_sources.py failed (exit code ${rc})\n"
|
||||||
|
"--- stdout ---\n${out}\n"
|
||||||
|
"--- stderr ---\n${err}\n"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(third_party/glslang)
|
||||||
|
|
||||||
|
|
||||||
|
target_link_libraries(TheChef PRIVATE nlohmann_json::nlohmann_json spdlog::spdlog glslang glslang-default-resource-limits)
|
||||||
|
|
||||||
|
# Optional: warnings
|
||||||
|
if (MSVC)
|
||||||
|
target_compile_options(TheChef PRIVATE /W4)
|
||||||
|
else()
|
||||||
|
target_compile_options(TheChef PRIVATE -Wall -Wextra -Wpedantic)
|
||||||
|
endif()
|
||||||
1
ShaderCompiler.cpp
Normal file
1
ShaderCompiler.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "ShaderCompiler.h"
|
||||||
250
ShaderCompiler.h
Normal file
250
ShaderCompiler.h
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
#ifndef SHADERCOMPILER_H
|
||||||
|
#define SHADERCOMPILER_H
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <regex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// glslang
|
||||||
|
#include <glslang/Public/ResourceLimits.h>
|
||||||
|
#include <glslang/Public/ShaderLang.h>
|
||||||
|
#include <SPIRV/GlslangToSpv.h>
|
||||||
|
#include <StandAlone/DirStackFileIncluder.h>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
class SpirvShaderCompiler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Options
|
||||||
|
{
|
||||||
|
std::vector<fs::path> includeDirs; // searched for #include "..."
|
||||||
|
std::vector<std::string> defines; // "NAME" or "NAME=VALUE"
|
||||||
|
bool debugInfo = false; // emit debug info in SPIR-V
|
||||||
|
glslang::EShTargetClientVersion vulkanTarget = glslang::EShTargetVulkan_1_2;
|
||||||
|
glslang::EShTargetLanguageVersion spirvTarget = glslang::EShTargetSpv_1_5;
|
||||||
|
int glslVersion = 450;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit SpirvShaderCompiler(Options opt = {})
|
||||||
|
: m_opt(std::move(opt))
|
||||||
|
{
|
||||||
|
ensureInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if rebuilt. Throws on compilation/dependency errors.
|
||||||
|
bool CompileFileIfNeeded(const fs::path& entryFile, const fs::path& outSpv)
|
||||||
|
{
|
||||||
|
if (!IsEntryShader(entryFile))
|
||||||
|
throw std::runtime_error("CompileFileIfNeeded called with non-entry shader: " + entryFile.string());
|
||||||
|
|
||||||
|
if (!NeedsRebuild(entryFile, outSpv))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const EShLanguage stage = StageFromExtension(entryFile);
|
||||||
|
|
||||||
|
auto spirv = CompileToSpirvWords(entryFile, stage);
|
||||||
|
WriteSpv(outSpv, spirv);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility for your cooker rules
|
||||||
|
static bool IsEntryShader(const fs::path& p)
|
||||||
|
{
|
||||||
|
const auto ext = ToLower(p.extension().string());
|
||||||
|
return ext == ".vert" || ext == ".frag";
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsIncludeOnlyShader(const fs::path& p)
|
||||||
|
{
|
||||||
|
return ToLower(p.extension().string()) == ".glsl";
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Options m_opt;
|
||||||
|
|
||||||
|
// --- init ---
|
||||||
|
static void ensureInitialized()
|
||||||
|
{
|
||||||
|
static bool initialized = false;
|
||||||
|
if (!initialized) {
|
||||||
|
glslang::InitializeProcess();
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- file helpers ---
|
||||||
|
static std::string ReadTextFile(const fs::path& p)
|
||||||
|
{
|
||||||
|
std::ifstream f(p, std::ios::in);
|
||||||
|
if (!f) throw std::runtime_error("Failed to open file: " + p.string());
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << f.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EnsureParentDir(const fs::path& p)
|
||||||
|
{
|
||||||
|
fs::create_directories(p.parent_path());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteSpv(const fs::path& out, const std::vector<uint32_t>& spirv)
|
||||||
|
{
|
||||||
|
EnsureParentDir(out);
|
||||||
|
std::ofstream f(out, std::ios::binary);
|
||||||
|
if (!f) throw std::runtime_error("Failed to write: " + out.string());
|
||||||
|
f.write(reinterpret_cast<const char*>(spirv.data()),
|
||||||
|
static_cast<std::streamsize>(spirv.size() * sizeof(uint32_t)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string ToLower(std::string s)
|
||||||
|
{
|
||||||
|
for (char& c : s) c = (char)std::tolower((unsigned char)c);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static EShLanguage StageFromExtension(const fs::path& p)
|
||||||
|
{
|
||||||
|
const auto ext = ToLower(p.extension().string());
|
||||||
|
if (ext == ".vert") return EShLangVertex;
|
||||||
|
if (ext == ".frag") return EShLangFragment;
|
||||||
|
throw std::runtime_error("Unknown entry shader stage extension: " + p.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string> FindIncludes(const std::string& text)
|
||||||
|
{
|
||||||
|
std::vector<std::string> out;
|
||||||
|
static const std::regex re(
|
||||||
|
R"(^\s*#\s*include\s*"([^"]+)\"\s*$)",
|
||||||
|
std::regex::icase
|
||||||
|
);
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
std::istringstream iss(text);
|
||||||
|
while (std::getline(iss, line)) {
|
||||||
|
// strip single-line comments
|
||||||
|
auto pos = line.find("//");
|
||||||
|
if (pos != std::string::npos) line = line.substr(0, pos);
|
||||||
|
|
||||||
|
std::smatch m;
|
||||||
|
if (std::regex_search(line, m, re)) {
|
||||||
|
out.push_back(m[1].str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path ResolveInclude(const fs::path& includingFile, const std::string& includeName) const
|
||||||
|
{
|
||||||
|
{
|
||||||
|
fs::path p = includingFile.parent_path() / includeName;
|
||||||
|
if (fs::exists(p)) return fs::weakly_canonical(p);
|
||||||
|
}
|
||||||
|
for (const auto& d : m_opt.includeDirs) {
|
||||||
|
fs::path p = d / includeName;
|
||||||
|
if (fs::exists(p)) return fs::weakly_canonical(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Failed to resolve include \"" + includeName + "\" included from " + includingFile.string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectDepsRecursive(const fs::path& file, std::unordered_set<std::string>& visitedAbs) const
|
||||||
|
{
|
||||||
|
fs::path abs = fs::weakly_canonical(file);
|
||||||
|
const std::string key = abs.string();
|
||||||
|
if (visitedAbs.contains(key)) return;
|
||||||
|
visitedAbs.insert(key);
|
||||||
|
|
||||||
|
const std::string text = ReadTextFile(abs);
|
||||||
|
for (const auto& inc : FindIncludes(text)) {
|
||||||
|
fs::path resolved = ResolveInclude(abs, inc);
|
||||||
|
CollectDepsRecursive(resolved, visitedAbs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::file_time_type NewestDependencyTime(const fs::path& entryFile) const
|
||||||
|
{
|
||||||
|
std::unordered_set<std::string> deps;
|
||||||
|
CollectDepsRecursive(entryFile, deps);
|
||||||
|
|
||||||
|
fs::file_time_type newest = fs::file_time_type::min();
|
||||||
|
for (const auto& dep : deps) {
|
||||||
|
auto t = fs::last_write_time(dep);
|
||||||
|
if (t > newest) newest = t;
|
||||||
|
}
|
||||||
|
return newest;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NeedsRebuild(const fs::path& entryFile, const fs::path& outSpv) const
|
||||||
|
{
|
||||||
|
if (!fs::exists(outSpv)) return true;
|
||||||
|
|
||||||
|
const auto outTime = fs::last_write_time(outSpv);
|
||||||
|
const auto newestDep = NewestDependencyTime(entryFile);
|
||||||
|
return newestDep > outTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- compilation ---
|
||||||
|
std::vector<uint32_t> CompileToSpirvWords(const fs::path& entryFile, EShLanguage stage) const
|
||||||
|
{
|
||||||
|
const std::string source = ReadTextFile(entryFile);
|
||||||
|
|
||||||
|
std::string preamble;
|
||||||
|
for (const auto& d : m_opt.defines)
|
||||||
|
preamble += "#define " + d + "\n";
|
||||||
|
|
||||||
|
const char* strings[] = { source.c_str() };
|
||||||
|
|
||||||
|
glslang::TShader shader(stage);
|
||||||
|
shader.setStrings(strings, 1);
|
||||||
|
shader.setPreamble(preamble.c_str());
|
||||||
|
shader.setEntryPoint("main");
|
||||||
|
shader.setSourceEntryPoint("main");
|
||||||
|
|
||||||
|
shader.setEnvInput(glslang::EShSourceGlsl, stage, glslang::EShClientVulkan, 100);
|
||||||
|
shader.setEnvClient(glslang::EShClientVulkan, m_opt.vulkanTarget);
|
||||||
|
shader.setEnvTarget(glslang::EShTargetSpv, m_opt.spirvTarget);
|
||||||
|
|
||||||
|
DirStackFileIncluder includer;
|
||||||
|
// Search local first, then include dirs:
|
||||||
|
includer.pushExternalLocalDirectory(entryFile.parent_path().string());
|
||||||
|
for (auto& d : m_opt.includeDirs)
|
||||||
|
includer.pushExternalLocalDirectory(d.string());
|
||||||
|
|
||||||
|
const TBuiltInResource* resources = GetDefaultResources();
|
||||||
|
const EShMessages messages = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules);
|
||||||
|
|
||||||
|
if (!shader.parse(resources, m_opt.glslVersion, false, messages, includer)) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"GLSL parse failed: " + entryFile.string() + "\n" +
|
||||||
|
shader.getInfoLog() + "\n" + shader.getInfoDebugLog()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
glslang::TProgram program;
|
||||||
|
program.addShader(&shader);
|
||||||
|
|
||||||
|
if (!program.link(messages)) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"GLSL link failed: " + entryFile.string() + "\n" +
|
||||||
|
program.getInfoLog() + "\n" + program.getInfoDebugLog()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint32_t> spirv;
|
||||||
|
glslang::SpvOptions spvOpt{};
|
||||||
|
spvOpt.generateDebugInfo = m_opt.debugInfo;
|
||||||
|
glslang::GlslangToSpv(*program.getIntermediate(stage), spirv, &spvOpt);
|
||||||
|
return spirv;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //SHADERCOMPILER_H
|
||||||
254
main.cpp
Normal file
254
main.cpp
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
// main.cpp (updated)
|
||||||
|
|
||||||
|
// Standard
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cctype>
|
||||||
|
#include <chrono>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
// Manifest
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
|
|
||||||
|
// Your shader compiler header
|
||||||
|
#include "ShaderCompiler.h"
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
static void ensure_parent_dir(const fs::path& p) {
|
||||||
|
fs::create_directories(p.parent_path());
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string to_lower(std::string s) {
|
||||||
|
for (char& c : s) c = (char)std::tolower((unsigned char)c);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Args {
|
||||||
|
fs::path input;
|
||||||
|
fs::path output;
|
||||||
|
bool clean = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static Args parse_args(int argc, char** argv) {
|
||||||
|
Args a;
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
std::string s = argv[i];
|
||||||
|
auto next = [&]() -> std::string {
|
||||||
|
if (i + 1 >= argc) throw std::runtime_error("Missing value after " + s);
|
||||||
|
return argv[++i];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (s == "--input") a.input = next();
|
||||||
|
else if (s == "--output") a.output = next();
|
||||||
|
else if (s == "--clean") a.clean = true;
|
||||||
|
else if (s == "--help" || s == "-h") {
|
||||||
|
spdlog::info("AssetCooker --input <dir> --output <dir> [--clean]");
|
||||||
|
std::exit(0);
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("Unknown arg: " + s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.input.empty() || a.output.empty())
|
||||||
|
throw std::runtime_error("Usage: AssetCooker --input <dir> --output <dir> [--clean]");
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string classify_type(const fs::path& src) {
|
||||||
|
const std::string ext = to_lower(src.extension().string());
|
||||||
|
if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".tga") return "texture";
|
||||||
|
// Treat these as "shader family" (entry: .vert/.frag/.comp, include-only: .glsl)
|
||||||
|
if (ext == ".vert" || ext == ".frag" || ext == ".comp" || ext == ".glsl") return "shader";
|
||||||
|
if (ext == ".wav" || ext == ".ogg" || ext == ".mp3") return "audio";
|
||||||
|
if (ext == ".gltf" || ext == ".glb" || ext == ".fbx" || ext == ".obj") return "mesh";
|
||||||
|
return "raw";
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool needs_rebuild(const fs::path& src, const fs::path& dst) {
|
||||||
|
if (!fs::exists(dst)) return true;
|
||||||
|
return fs::last_write_time(src) > fs::last_write_time(dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool copy_if_needed(const fs::path& src, const fs::path& dst) {
|
||||||
|
if (!needs_rebuild(src, dst)) return false;
|
||||||
|
ensure_parent_dir(dst);
|
||||||
|
fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose your SPV output naming scheme.
|
||||||
|
// Option A: shader.vert -> shader.vert.spv (keeps original extension)
|
||||||
|
// Option B: shader.vert -> shader.spv (replace with .spv)
|
||||||
|
static fs::path make_spv_output_path(const fs::path& dstLikeSrc) {
|
||||||
|
fs::path out = dstLikeSrc;
|
||||||
|
// Option A:
|
||||||
|
out.replace_extension(dstLikeSrc.extension().string() + ".spv");
|
||||||
|
// Option B (uncomment to use):
|
||||||
|
// out.replace_extension(".spv");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::int64_t filetime_to_epoch_ns(const fs::file_time_type& ft) {
|
||||||
|
return (std::int64_t)std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
ft.time_since_epoch()
|
||||||
|
).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
auto log = spdlog::stdout_color_mt("AssetCooker");
|
||||||
|
spdlog::set_default_logger(log);
|
||||||
|
|
||||||
|
spdlog::set_pattern("[%H:%M:%S] [%^%l%$] %v");
|
||||||
|
// spdlog::set_level(spdlog::level::debug);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const Args args = parse_args(argc, argv);
|
||||||
|
|
||||||
|
if (!fs::exists(args.input) || !fs::is_directory(args.input)) {
|
||||||
|
spdlog::error("Input dir does not exist: {}", args.input.string());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.clean && fs::exists(args.output)) {
|
||||||
|
spdlog::info("Cleaning output dir: {}", args.output.string());
|
||||||
|
fs::remove_all(args.output);
|
||||||
|
}
|
||||||
|
fs::create_directories(args.output);
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Shader compiler setup
|
||||||
|
// ----------------------------
|
||||||
|
SpirvShaderCompiler::Options scOpt;
|
||||||
|
scOpt.debugInfo = false; // set true if you want debug info in SPIR-V
|
||||||
|
|
||||||
|
// Common conventions (adjust to your folder layout):
|
||||||
|
// - shaders live under <input>/shaders
|
||||||
|
// - includes live under <input>/shaders/include
|
||||||
|
//
|
||||||
|
// If you don't have these folders, either remove these or point them elsewhere.
|
||||||
|
scOpt.includeDirs = {
|
||||||
|
args.input / "shaders",
|
||||||
|
args.input / "shaders" / "include"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Optional defines:
|
||||||
|
// scOpt.defines = { "MY_DEFINE=1", "USE_FOG" };
|
||||||
|
|
||||||
|
SpirvShaderCompiler shaderCompiler(scOpt);
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Manifest init
|
||||||
|
// ----------------------------
|
||||||
|
json manifest;
|
||||||
|
manifest["version"] = 1;
|
||||||
|
manifest["assets"] = json::array();
|
||||||
|
|
||||||
|
std::size_t cooked = 0, skipped = 0;
|
||||||
|
|
||||||
|
for (auto it = fs::recursive_directory_iterator(args.input);
|
||||||
|
it != fs::recursive_directory_iterator();
|
||||||
|
++it) {
|
||||||
|
|
||||||
|
if (!it->is_regular_file()) continue;
|
||||||
|
|
||||||
|
fs::path src = it->path();
|
||||||
|
fs::path rel = fs::relative(src, args.input);
|
||||||
|
fs::path dst = args.output / rel;
|
||||||
|
|
||||||
|
const std::string type = classify_type(src);
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
fs::path finalOut = dst; // what actually ends up in output (if any)
|
||||||
|
std::string finalType = type; // stored in manifest
|
||||||
|
|
||||||
|
if (type == "shader") {
|
||||||
|
// Include-only shader (.glsl): typically NOT cooked to output.
|
||||||
|
if (SpirvShaderCompiler::IsIncludeOnlyShader(src)) {
|
||||||
|
changed = false;
|
||||||
|
finalType = "shader_include";
|
||||||
|
// If you *do* want to copy includes, uncomment:
|
||||||
|
// changed = copy_if_needed(src, dst);
|
||||||
|
// finalOut = dst;
|
||||||
|
}
|
||||||
|
// Entry shader (.vert/.frag): compile to SPIR-V.
|
||||||
|
else if (SpirvShaderCompiler::IsEntryShader(src)) {
|
||||||
|
fs::path spvOut = make_spv_output_path(dst);
|
||||||
|
changed = shaderCompiler.CompileFileIfNeeded(src, spvOut);
|
||||||
|
|
||||||
|
finalOut = spvOut;
|
||||||
|
finalType = "shader_spirv";
|
||||||
|
}
|
||||||
|
// Something else classified as shader (ex: .comp in your classifier)
|
||||||
|
// but not supported by the compiler yet -> copy raw as fallback.
|
||||||
|
else {
|
||||||
|
changed = copy_if_needed(src, dst);
|
||||||
|
finalOut = dst;
|
||||||
|
finalType = "shader_raw";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Normal assets: copy if needed.
|
||||||
|
changed = copy_if_needed(src, dst);
|
||||||
|
finalOut = dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
cooked++;
|
||||||
|
spdlog::debug("Cooked: {} -> {}",
|
||||||
|
rel.generic_string(),
|
||||||
|
fs::relative(finalOut, args.output).generic_string());
|
||||||
|
} else {
|
||||||
|
skipped++;
|
||||||
|
spdlog::debug("Skipped (up-to-date): {}", rel.generic_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Manifest entry
|
||||||
|
// ----------------------------
|
||||||
|
json entry;
|
||||||
|
entry["src"] = rel.generic_string(); // relative to input
|
||||||
|
entry["type"] = finalType;
|
||||||
|
|
||||||
|
// If this asset produced an output file, record it.
|
||||||
|
// If not (e.g. shader includes), set out to null.
|
||||||
|
if (fs::exists(finalOut) && fs::is_regular_file(finalOut)) {
|
||||||
|
entry["out"] = fs::relative(finalOut, args.output).generic_string();
|
||||||
|
entry["size_bytes"] = (std::uintmax_t)fs::file_size(finalOut);
|
||||||
|
entry["mtime_epoch_ns"] = filetime_to_epoch_ns(fs::last_write_time(finalOut));
|
||||||
|
} else {
|
||||||
|
entry["out"] = nullptr;
|
||||||
|
entry["size_bytes"] = (std::uintmax_t)fs::file_size(src);
|
||||||
|
entry["mtime_epoch_ns"] = filetime_to_epoch_ns(fs::last_write_time(src));
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest["assets"].push_back(std::move(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write manifest.json
|
||||||
|
{
|
||||||
|
fs::path manifestPath = args.output / "manifest.json";
|
||||||
|
std::ofstream out(manifestPath);
|
||||||
|
if (!out) {
|
||||||
|
throw std::runtime_error("Failed to write: " + manifestPath.string());
|
||||||
|
}
|
||||||
|
out << manifest.dump(2) << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
spdlog::info("Cook done. cooked={} skipped={} output={}",
|
||||||
|
cooked, skipped, args.output.string());
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::critical("Cook failed: {}", e.what());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
third_party/glslang
vendored
Submodule
1
third_party/glslang
vendored
Submodule
Submodule third_party/glslang added at 7881226269
1
third_party/json
vendored
Submodule
1
third_party/json
vendored
Submodule
Submodule third_party/json added at 2bb9d59fde
1
third_party/spdlog
vendored
Submodule
1
third_party/spdlog
vendored
Submodule
Submodule third_party/spdlog added at 687226d95d
Reference in New Issue
Block a user