Compare commits
1
Commits
38230f5092
...
0273340e3b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0273340e3b |
+14
-1
@@ -3,7 +3,7 @@ add_executable(TheChef
|
|||||||
"ShaderCompiler.h" "ShaderCompiler.cpp"
|
"ShaderCompiler.h" "ShaderCompiler.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_features(TheChef PRIVATE cxx_std_17)
|
target_compile_features(TheChef PRIVATE cxx_std_20)
|
||||||
|
|
||||||
# Good practice for tools
|
# Good practice for tools
|
||||||
target_compile_definitions(TheChef PRIVATE
|
target_compile_definitions(TheChef PRIVATE
|
||||||
@@ -45,6 +45,19 @@ add_subdirectory(third_party/glslang)
|
|||||||
|
|
||||||
target_link_libraries(TheChef PRIVATE nlohmann_json::nlohmann_json spdlog::spdlog glslang glslang-default-resource-limits)
|
target_link_libraries(TheChef PRIVATE nlohmann_json::nlohmann_json spdlog::spdlog glslang glslang-default-resource-limits)
|
||||||
|
|
||||||
|
# Model cooking needs the engine's asset types, so it's only available when built inside Destrum
|
||||||
|
if (TARGET destrum::assets)
|
||||||
|
target_sources(TheChef PRIVATE "ModelCooker.h" "ModelCooker.cpp")
|
||||||
|
target_link_libraries(TheChef PRIVATE destrum::assets)
|
||||||
|
target_compile_definitions(TheChef PRIVATE THECHEF_MODEL_COOKING)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (TARGET destrum::assets AND TARGET tinyexr)
|
||||||
|
target_sources(TheChef PRIVATE "TextureCooker.h" "TextureCooker.cpp")
|
||||||
|
target_link_libraries(TheChef PRIVATE destrum::assets tinyexr)
|
||||||
|
target_compile_definitions(TheChef PRIVATE THECHEF_TEXTURE_COOKING)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Optional: warnings
|
# Optional: warnings
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
target_compile_options(TheChef PRIVATE /W4)
|
target_compile_options(TheChef PRIVATE /W4)
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#include "ModelCooker.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <destrum/Assets/ModelFile.h>
|
||||||
|
|
||||||
|
ModelCooker::ModelCooker(Options opt)
|
||||||
|
: m_opt(std::move(opt))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModelCooker::CookFileIfNeeded(const fs::path& source, const fs::path& output) const
|
||||||
|
{
|
||||||
|
if (!NeedsRebuild(source, output))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ModelDoc::ModelAsset model = ModelDoc::LoadModel(source.generic_string(), ModelFile::CookOptions);
|
||||||
|
MakePathsPortable(model, source);
|
||||||
|
|
||||||
|
fs::create_directories(output.parent_path());
|
||||||
|
ModelFile::WriteToFile(model, output);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path ModelCooker::MakeOutputPath(const fs::path& dstLikeSrc)
|
||||||
|
{
|
||||||
|
fs::path out = dstLikeSrc;
|
||||||
|
out.replace_extension(dstLikeSrc.extension().string() + std::string(ModelFile::Extension));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModelCooker::NeedsRebuild(const fs::path& source, const fs::path& output)
|
||||||
|
{
|
||||||
|
if (!fs::exists(output))
|
||||||
|
return true;
|
||||||
|
if (fs::last_write_time(source) > fs::last_write_time(output))
|
||||||
|
return true;
|
||||||
|
return ModelFile::ReadVersionFromFile(output) != ModelFile::Version;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelCooker::MakePathsPortable(ModelDoc::ModelAsset& model, const fs::path& source) const
|
||||||
|
{
|
||||||
|
const fs::path inputRoot = fs::absolute(m_opt.inputRoot).lexically_normal();
|
||||||
|
const fs::path modelPath = fs::absolute(source).lexically_normal();
|
||||||
|
const fs::path modelDir = modelPath.parent_path();
|
||||||
|
const std::string reference = modelPath.lexically_relative(inputRoot).generic_string();
|
||||||
|
|
||||||
|
model.sourcePath = reference;
|
||||||
|
model.skeleton.assetReference.path = reference;
|
||||||
|
|
||||||
|
for (ModelDoc::MeshPrimitive& primitive : model.primitives)
|
||||||
|
{
|
||||||
|
primitive.mesh.assetReference.path = reference;
|
||||||
|
if (primitive.mesh.skeleton)
|
||||||
|
primitive.mesh.skeleton->assetReference.path = reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ModelDoc::MaterialInfo& material : model.materials)
|
||||||
|
{
|
||||||
|
material.assetReference.path = reference;
|
||||||
|
|
||||||
|
for (ModelDoc::TextureInfo& texture : material.textures)
|
||||||
|
{
|
||||||
|
if (texture.embedded || texture.path.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const fs::path texturePath = fs::path(texture.path).lexically_normal();
|
||||||
|
const fs::path fromRoot = texturePath.lexically_relative(inputRoot);
|
||||||
|
if (fromRoot.empty() || *fromRoot.begin() == "..")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
texture.path = texturePath.lexically_relative(modelDir).generic_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SkeletalAnimation& animation : model.animations)
|
||||||
|
animation.assetReference.path = reference;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include <destrum/Util/ModelDoc.h>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
class ModelCooker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Options
|
||||||
|
{
|
||||||
|
fs::path inputRoot;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit ModelCooker(Options opt);
|
||||||
|
|
||||||
|
bool CookFileIfNeeded(const fs::path& source, const fs::path& output) const;
|
||||||
|
|
||||||
|
static fs::path MakeOutputPath(const fs::path& dstLikeSrc);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Options m_opt;
|
||||||
|
|
||||||
|
static bool NeedsRebuild(const fs::path& source, const fs::path& output);
|
||||||
|
void MakePathsPortable(ModelDoc::ModelAsset& model, const fs::path& source) const;
|
||||||
|
};
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#include "TextureCooker.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <tinyexr.h>
|
||||||
|
|
||||||
|
#include <destrum/Assets/TextureFile.h>
|
||||||
|
|
||||||
|
bool TextureCooker::CookFileIfNeeded(const fs::path& source, const fs::path& output) const
|
||||||
|
{
|
||||||
|
if (!NeedsRebuild(source, output))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
float* rgba = nullptr;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
const char* error = nullptr;
|
||||||
|
|
||||||
|
if (LoadEXR(&rgba, &width, &height, source.string().c_str(), &error) != TINYEXR_SUCCESS)
|
||||||
|
{
|
||||||
|
const std::string message = error ? error : "unknown error";
|
||||||
|
FreeEXRErrorMessage(error);
|
||||||
|
throw std::runtime_error("TinyEXR failed to load '" + source.string() + "': " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::unique_ptr<float, decltype(&std::free)> pixels(rgba, &std::free);
|
||||||
|
const std::size_t floatCount = static_cast<std::size_t>(width) * static_cast<std::size_t>(height) * 4;
|
||||||
|
|
||||||
|
const TextureFile::Texture texture = TextureFile::FromRGBA32Float(
|
||||||
|
static_cast<std::uint32_t>(width),
|
||||||
|
static_cast<std::uint32_t>(height),
|
||||||
|
std::span<const float>(pixels.get(), floatCount));
|
||||||
|
|
||||||
|
fs::create_directories(output.parent_path());
|
||||||
|
TextureFile::WriteToFile(texture, output);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path TextureCooker::MakeOutputPath(const fs::path& dstLikeSrc)
|
||||||
|
{
|
||||||
|
fs::path out = dstLikeSrc;
|
||||||
|
out.replace_extension(dstLikeSrc.extension().string() + std::string(TextureFile::Extension));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextureCooker::NeedsRebuild(const fs::path& source, const fs::path& output)
|
||||||
|
{
|
||||||
|
if (!fs::exists(output))
|
||||||
|
return true;
|
||||||
|
if (fs::last_write_time(source) > fs::last_write_time(output))
|
||||||
|
return true;
|
||||||
|
return TextureFile::ReadVersionFromFile(output) != TextureFile::Version;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
class TextureCooker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Returns true if rebuilt. Throws when the image can't be decoded.
|
||||||
|
bool CookFileIfNeeded(const fs::path& source, const fs::path& output) const;
|
||||||
|
|
||||||
|
static fs::path MakeOutputPath(const fs::path& dstLikeSrc);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool NeedsRebuild(const fs::path& source, const fs::path& output);
|
||||||
|
};
|
||||||
@@ -13,6 +13,14 @@
|
|||||||
|
|
||||||
#include "ShaderCompiler.h"
|
#include "ShaderCompiler.h"
|
||||||
|
|
||||||
|
#ifdef THECHEF_MODEL_COOKING
|
||||||
|
#include "ModelCooker.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef THECHEF_TEXTURE_COOKING
|
||||||
|
#include "TextureCooker.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
@@ -60,6 +68,7 @@ static Args parse_args(int argc, char** argv) {
|
|||||||
static std::string classify_type(const fs::path& src) {
|
static std::string classify_type(const fs::path& src) {
|
||||||
const std::string ext = to_lower(src.extension().string());
|
const std::string ext = to_lower(src.extension().string());
|
||||||
if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".tga") return "texture";
|
if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".tga") return "texture";
|
||||||
|
if (ext == ".exr") return "hdr_texture";
|
||||||
// Treat these as "shader family" (entry: .vert/.frag/.comp, include-only: .glsl)
|
// 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 == ".vert" || ext == ".frag" || ext == ".comp" || ext == ".glsl") return "shader";
|
||||||
if (ext == ".wav" || ext == ".ogg" || ext == ".mp3") return "audio";
|
if (ext == ".wav" || ext == ".ogg" || ext == ".mp3") return "audio";
|
||||||
@@ -79,15 +88,9 @@ static bool copy_if_needed(const fs::path& src, const fs::path& dst) {
|
|||||||
return true;
|
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) {
|
static fs::path make_spv_output_path(const fs::path& dstLikeSrc) {
|
||||||
fs::path out = dstLikeSrc;
|
fs::path out = dstLikeSrc;
|
||||||
// Option A:
|
|
||||||
out.replace_extension(dstLikeSrc.extension().string() + ".spv");
|
out.replace_extension(dstLikeSrc.extension().string() + ".spv");
|
||||||
// Option B (uncomment to use):
|
|
||||||
// out.replace_extension(".spv");
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,32 +122,29 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
fs::create_directories(args.output);
|
fs::create_directories(args.output);
|
||||||
|
|
||||||
// ----------------------------
|
|
||||||
// Shader compiler setup
|
|
||||||
// ----------------------------
|
|
||||||
SpirvShaderCompiler::Options scOpt;
|
SpirvShaderCompiler::Options scOpt;
|
||||||
scOpt.debugInfo = false; // set true if you want debug info in SPIR-V
|
scOpt.debugInfo = false; // set true if you want debug info in SPIR-V
|
||||||
scOpt.defines = { "DEBUG=1" };
|
scOpt.defines = { "DEBUG=1" };
|
||||||
|
|
||||||
|
|
||||||
// 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 = {
|
scOpt.includeDirs = {
|
||||||
args.input / "shaders",
|
args.input / "shaders",
|
||||||
args.input / "shaders" / "include"
|
args.input / "shaders" / "include"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Optional defines:
|
|
||||||
// scOpt.defines = { "MY_DEFINE=1", "USE_FOG" };
|
|
||||||
|
|
||||||
SpirvShaderCompiler shaderCompiler(scOpt);
|
SpirvShaderCompiler shaderCompiler(scOpt);
|
||||||
|
|
||||||
// ----------------------------
|
#ifdef THECHEF_MODEL_COOKING
|
||||||
// Manifest init
|
ModelCooker::Options mcOpt;
|
||||||
// ----------------------------
|
mcOpt.inputRoot = args.input;
|
||||||
|
|
||||||
|
ModelCooker modelCooker(mcOpt);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef THECHEF_TEXTURE_COOKING
|
||||||
|
TextureCooker textureCooker;
|
||||||
|
#endif
|
||||||
|
|
||||||
json manifest;
|
json manifest;
|
||||||
manifest["version"] = 1;
|
manifest["version"] = 1;
|
||||||
manifest["assets"] = json::array();
|
manifest["assets"] = json::array();
|
||||||
@@ -191,6 +191,40 @@ int main(int argc, char** argv) {
|
|||||||
finalOut = dst;
|
finalOut = dst;
|
||||||
finalType = "shader_raw";
|
finalType = "shader_raw";
|
||||||
}
|
}
|
||||||
|
} else if (type == "mesh") {
|
||||||
|
// The source model is still copied for code that imports it directly.
|
||||||
|
changed = copy_if_needed(src, dst);
|
||||||
|
finalOut = dst;
|
||||||
|
|
||||||
|
#ifdef THECHEF_MODEL_COOKING
|
||||||
|
// Models that can't be imported on their own (e.g. animation-only files) keep using the source.
|
||||||
|
const fs::path modelOut = ModelCooker::MakeOutputPath(dst);
|
||||||
|
try {
|
||||||
|
changed = modelCooker.CookFileIfNeeded(src, modelOut) || changed;
|
||||||
|
|
||||||
|
finalOut = modelOut;
|
||||||
|
finalType = "model";
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::warn("Not cooking {}, using the source model instead: {}", rel.generic_string(), e.what());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} else if (type == "hdr_texture") {
|
||||||
|
#ifdef THECHEF_TEXTURE_COOKING
|
||||||
|
const fs::path textureOut = TextureCooker::MakeOutputPath(dst);
|
||||||
|
try {
|
||||||
|
changed = textureCooker.CookFileIfNeeded(src, textureOut);
|
||||||
|
|
||||||
|
finalOut = textureOut;
|
||||||
|
finalType = "texture_cooked";
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::warn("Not cooking {}, copying the source image instead: {}", rel.generic_string(), e.what());
|
||||||
|
changed = copy_if_needed(src, dst);
|
||||||
|
finalOut = dst;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
changed = copy_if_needed(src, dst);
|
||||||
|
finalOut = dst;
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
// Normal assets: copy if needed.
|
// Normal assets: copy if needed.
|
||||||
changed = copy_if_needed(src, dst);
|
changed = copy_if_needed(src, dst);
|
||||||
|
|||||||
Reference in New Issue
Block a user