#include #define STB_IMAGE_IMPLEMENTATION #include #define TINYEXR_IMPLEMENTATION #include "tinyexr.h" #include // fprintf #include // free #include "spdlog/spdlog.h" #include ImageData::~ImageData() { freePixels(); } void ImageData::freePixels() { if (shouldSTBFree) { if (pixels) stbi_image_free(pixels); if (hdrPixels) stbi_image_free(hdrPixels); spdlog::debug("Freed STB image memory"); } if (shouldEXRFree) { if (hdrPixels) free(hdrPixels); spdlog::debug("Freed TinyEXR image memory"); } cookedPixels.clear(); pixels = nullptr; hdrPixels = nullptr; shouldSTBFree = false; shouldEXRFree = false; } namespace util { static bool isExrExt(const std::filesystem::path& p) { auto ext = p.extension().string(); for (auto& c : ext) c = char(::tolower(c)); return ext == ".exr"; } static bool isCookedTextureExt(const std::filesystem::path& p) { auto ext = p.extension().string(); for (auto& c : ext) c = char(::tolower(c)); return ext == TextureFile::Extension; } static VkFormat toVkFormat(TextureFile::PixelFormat format, TextureIntent intent) { switch (format) { case TextureFile::PixelFormat::RGBA16Float: return VK_FORMAT_R16G16B16A16_SFLOAT; case TextureFile::PixelFormat::RGBA8: return intent == TextureIntent::ColorSrgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM; } return VK_FORMAT_UNDEFINED; } // TheChef writes .dtex next to the copied source, so every caller that has the source path gets it static std::filesystem::path findCookedSibling(const std::filesystem::path& p) { std::filesystem::path cooked = p; cooked += TextureFile::Extension; std::error_code error; if (!std::filesystem::is_regular_file(cooked, error)) { return {}; } const auto sourceTime = std::filesystem::last_write_time(p, error); if (!error && sourceTime > std::filesystem::last_write_time(cooked, error)) { return {}; } return cooked; } ImageData loadImage(const std::filesystem::path& p, TextureIntent intent) { ImageData data; if (!isCookedTextureExt(p)) { if (const auto cooked = findCookedSibling(p); !cooked.empty()) { if (ImageData cookedData = loadImage(cooked, intent); cookedData.byteSize != 0) { return cookedData; } } } if (isCookedTextureExt(p)) { try { TextureFile::Texture texture = TextureFile::ReadFromFile(p); data.cookedPixels = std::move(texture.pixels); data.pixels = data.cookedPixels.data(); data.width = static_cast(texture.width); data.height = static_cast(texture.height); data.channels = 4; data.vkFormat = toVkFormat(texture.format, intent); data.byteSize = data.cookedPixels.size(); } catch (const std::exception& exception) { spdlog::error("Failed to read cooked texture '{}': {}", p.string(), exception.what()); return {}; } return data; } if (isExrExt(p)) { float* out = nullptr; int w = 0, h = 0; const char* err = nullptr; const int ret = LoadEXR(&out, &w, &h, p.string().c_str(), &err); if (ret != TINYEXR_SUCCESS || !out || w <= 0 || h <= 0) { if (err) { spdlog::error("TinyEXR error: {}", err); FreeEXRErrorMessage(err); } return {}; } data.hdr = true; data.hdrPixels = out; data.width = w; data.height = h; data.channels = 4; data.shouldEXRFree = true; data.vkFormat = VK_FORMAT_R32G32B32A32_SFLOAT; data.byteSize = std::size_t(w) * std::size_t(h) * 4 * sizeof(float); return data; } // ---------- STB HDR (.hdr etc) ---------- data.shouldSTBFree = true; if (stbi_is_hdr(p.string().c_str())) { data.hdr = true; data.hdrPixels = stbi_loadf( p.string().c_str(), &data.width, &data.height, &data.comp, 4 ); if (!data.hdrPixels || data.width <= 0 || data.height <= 0) { return {}; } data.channels = 4; data.vkFormat = VK_FORMAT_R32G32B32A32_SFLOAT; data.byteSize = std::size_t(data.width) * std::size_t(data.height) * 4 * sizeof(float); return data; } // ---------- STB LDR (png/jpg/...) ---------- data.pixels = stbi_load( p.string().c_str(), &data.width, &data.height, &data.channels, 4 ); if (!data.pixels || data.width <= 0 || data.height <= 0) { return {}; } data.channels = 4; // Choose color space for LDR based on intent switch (intent) { case TextureIntent::ColorSrgb: data.vkFormat = VK_FORMAT_R8G8B8A8_SRGB; break; case TextureIntent::DataLinear: data.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; break; } data.byteSize = std::size_t(data.width) * std::size_t(data.height) * 4 * sizeof(unsigned char); return data; } } // namespace util