feat: add more image types to load from cooked files
This commit is contained in:
+1
-1
Submodule TheChef updated: 0273340e3b...1981a21481
@@ -14,6 +14,7 @@ namespace TextureFile {
|
|||||||
|
|
||||||
enum class PixelFormat : std::uint32_t {
|
enum class PixelFormat : std::uint32_t {
|
||||||
RGBA16Float,
|
RGBA16Float,
|
||||||
|
RGBA8,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Texture {
|
struct Texture {
|
||||||
@@ -26,6 +27,7 @@ namespace TextureFile {
|
|||||||
[[nodiscard]] std::size_t BytesPerPixel(PixelFormat format);
|
[[nodiscard]] std::size_t BytesPerPixel(PixelFormat format);
|
||||||
|
|
||||||
[[nodiscard]] Texture FromRGBA32Float(std::uint32_t width, std::uint32_t height, std::span<const float> rgba);
|
[[nodiscard]] Texture FromRGBA32Float(std::uint32_t width, std::uint32_t height, std::span<const float> rgba);
|
||||||
|
[[nodiscard]] Texture FromRGBA8(std::uint32_t width, std::uint32_t height, std::span<const std::uint8_t> rgba);
|
||||||
|
|
||||||
[[nodiscard]] std::vector<std::uint8_t> Write(const Texture& texture);
|
[[nodiscard]] std::vector<std::uint8_t> Write(const Texture& texture);
|
||||||
[[nodiscard]] Texture Read(std::span<const std::uint8_t> bytes);
|
[[nodiscard]] Texture Read(std::span<const std::uint8_t> bytes);
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ std::size_t TextureFile::BytesPerPixel(PixelFormat format) {
|
|||||||
switch (format) {
|
switch (format) {
|
||||||
case PixelFormat::RGBA16Float:
|
case PixelFormat::RGBA16Float:
|
||||||
return 4 * sizeof(std::uint16_t);
|
return 4 * sizeof(std::uint16_t);
|
||||||
|
case PixelFormat::RGBA8:
|
||||||
|
return 4;
|
||||||
}
|
}
|
||||||
throw std::runtime_error("Unknown cooked texture pixel format");
|
throw std::runtime_error("Unknown cooked texture pixel format");
|
||||||
}
|
}
|
||||||
@@ -46,6 +48,19 @@ TextureFile::Texture TextureFile::FromRGBA32Float(std::uint32_t width, std::uint
|
|||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextureFile::Texture TextureFile::FromRGBA8(std::uint32_t width, std::uint32_t height, std::span<const std::uint8_t> rgba) {
|
||||||
|
if (rgba.size() != std::size_t{width} * height * 4) {
|
||||||
|
throw std::invalid_argument("RGBA pixel data doesn't match the texture size");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Texture{
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.format = PixelFormat::RGBA8,
|
||||||
|
.pixels = {rgba.begin(), rgba.end()},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::uint8_t> TextureFile::Write(const Texture& texture) {
|
std::vector<std::uint8_t> TextureFile::Write(const Texture& texture) {
|
||||||
if (texture.pixels.size() != ExpectedByteSize(texture)) {
|
if (texture.pixels.size() != ExpectedByteSize(texture)) {
|
||||||
throw std::invalid_argument("Texture pixel data doesn't match its size and format");
|
throw std::invalid_argument("Texture pixel data doesn't match its size and format");
|
||||||
|
|||||||
@@ -53,19 +53,44 @@ namespace util
|
|||||||
return ext == TextureFile::Extension;
|
return ext == TextureFile::Extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VkFormat toVkFormat(TextureFile::PixelFormat format)
|
static VkFormat toVkFormat(TextureFile::PixelFormat format, TextureIntent intent)
|
||||||
{
|
{
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case TextureFile::PixelFormat::RGBA16Float: return VK_FORMAT_R16G16B16A16_SFLOAT;
|
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;
|
return VK_FORMAT_UNDEFINED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TheChef writes <image>.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 loadImage(const std::filesystem::path& p, TextureIntent intent)
|
||||||
{
|
{
|
||||||
ImageData data;
|
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)) {
|
if (isCookedTextureExt(p)) {
|
||||||
try {
|
try {
|
||||||
@@ -77,7 +102,7 @@ namespace util
|
|||||||
data.height = static_cast<int>(texture.height);
|
data.height = static_cast<int>(texture.height);
|
||||||
data.channels = 4;
|
data.channels = 4;
|
||||||
|
|
||||||
data.vkFormat = toVkFormat(texture.format);
|
data.vkFormat = toVkFormat(texture.format, intent);
|
||||||
data.byteSize = data.cookedPixels.size();
|
data.byteSize = data.cookedPixels.size();
|
||||||
} catch (const std::exception& exception) {
|
} catch (const std::exception& exception) {
|
||||||
spdlog::error("Failed to read cooked texture '{}': {}", p.string(), exception.what());
|
spdlog::error("Failed to read cooked texture '{}': {}", p.string(), exception.what());
|
||||||
|
|||||||
@@ -432,6 +432,32 @@ namespace {
|
|||||||
"cooked texture pixels must be uploaded as stored");
|
"cooked texture pixels must be uploaded as stored");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testLdrTextureFile()
|
||||||
|
{
|
||||||
|
const std::vector<std::uint8_t> rgba{255, 0, 0, 255, 0, 128, 255, 64};
|
||||||
|
const auto texture = TextureFile::FromRGBA8(2, 1, rgba);
|
||||||
|
const auto loaded = TextureFile::Read(TextureFile::Write(texture));
|
||||||
|
check(loaded.format == TextureFile::PixelFormat::RGBA8 && loaded.pixels == rgba,
|
||||||
|
"8 bit cooked textures must round-trip");
|
||||||
|
|
||||||
|
const auto dir = std::filesystem::temp_directory_path() / "destrum_ldr_texture_test";
|
||||||
|
std::filesystem::create_directories(dir);
|
||||||
|
const auto source = dir / "missing_source.png";
|
||||||
|
auto cooked = source;
|
||||||
|
cooked += TextureFile::Extension;
|
||||||
|
TextureFile::WriteToFile(texture, cooked);
|
||||||
|
|
||||||
|
const auto color = util::loadImage(source, TextureIntent::ColorSrgb);
|
||||||
|
const auto data = util::loadImage(source, TextureIntent::DataLinear);
|
||||||
|
std::filesystem::remove_all(dir);
|
||||||
|
|
||||||
|
check(color.vkFormat == VK_FORMAT_R8G8B8A8_SRGB && data.vkFormat == VK_FORMAT_R8G8B8A8_UNORM,
|
||||||
|
"8 bit cooked textures must pick their color space from the intent");
|
||||||
|
check(color.width == 2 && color.height == 1 && color.pixels != nullptr &&
|
||||||
|
std::memcmp(color.pixels, rgba.data(), rgba.size()) == 0,
|
||||||
|
"loading a source image must use its cooked sibling");
|
||||||
|
}
|
||||||
|
|
||||||
void testAssetReferenceCacheKeys()
|
void testAssetReferenceCacheKeys()
|
||||||
{
|
{
|
||||||
const AssetReference reference{
|
const AssetReference reference{
|
||||||
@@ -528,6 +554,7 @@ void RunAssetsTests()
|
|||||||
testAssetManagerLoadsCookedModels();
|
testAssetManagerLoadsCookedModels();
|
||||||
testTextureFileRoundTrip();
|
testTextureFileRoundTrip();
|
||||||
testImageLoaderReadsCookedTextures();
|
testImageLoaderReadsCookedTextures();
|
||||||
|
testLdrTextureFile();
|
||||||
testAssetReferenceCacheKeys();
|
testAssetReferenceCacheKeys();
|
||||||
testAssetGuids();
|
testAssetGuids();
|
||||||
testAssetDatabaseFollowsMovedAssets();
|
testAssetDatabaseFollowsMovedAssets();
|
||||||
|
|||||||
Reference in New Issue
Block a user