#include #include #include #include "BinaryStream.h" namespace { using AssetBinary::BinaryReader; using AssetBinary::BinaryWriter; constexpr AssetBinary::Magic Magic{'D', 'T', 'E', 'X'}; constexpr std::string_view Kind = "texture"; std::size_t ExpectedByteSize(const TextureFile::Texture& texture) { return std::size_t{texture.width} * texture.height * TextureFile::BytesPerPixel(texture.format); } } std::size_t TextureFile::BytesPerPixel(PixelFormat format) { switch (format) { case PixelFormat::RGBA16Float: return 4 * sizeof(std::uint16_t); case PixelFormat::RGBA8: return 4; } throw std::runtime_error("Unknown cooked texture pixel format"); } TextureFile::Texture TextureFile::FromRGBA32Float(std::uint32_t width, std::uint32_t height, std::span rgba) { const std::size_t pixelCount = std::size_t{width} * height; if (rgba.size() != pixelCount * 4) { throw std::invalid_argument("RGBA pixel data doesn't match the texture size"); } Texture texture{ .width = width, .height = height, .format = PixelFormat::RGBA16Float, }; texture.pixels.resize(pixelCount * BytesPerPixel(texture.format)); for (std::size_t i = 0; i < pixelCount; ++i) { const glm::vec4 color{rgba[i * 4 + 0], rgba[i * 4 + 1], rgba[i * 4 + 2], rgba[i * 4 + 3]}; const std::uint64_t packed = glm::packHalf4x16(color); std::memcpy(&texture.pixels[i * sizeof(packed)], &packed, sizeof(packed)); } return texture; } TextureFile::Texture TextureFile::FromRGBA8(std::uint32_t width, std::uint32_t height, std::span 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 TextureFile::Write(const Texture& texture) { if (texture.pixels.size() != ExpectedByteSize(texture)) { throw std::invalid_argument("Texture pixel data doesn't match its size and format"); } BinaryWriter writer; writer.WriteHeader(Magic, Version); writer.Write(texture.width); writer.Write(texture.height); writer.Write(static_cast(texture.format)); writer.WriteArray(texture.pixels); return writer.TakeBytes(); } TextureFile::Texture TextureFile::Read(std::span bytes) { BinaryReader reader{bytes}; reader.ReadHeader(Magic, Version, Kind); Texture texture; texture.width = reader.Read(); texture.height = reader.Read(); texture.format = static_cast(reader.Read()); texture.pixels = reader.ReadArray(); reader.ExpectEnd(Kind); if (texture.pixels.size() != ExpectedByteSize(texture)) { throw std::runtime_error("Cooked texture file is corrupt: pixel data doesn't match its size"); } return texture; } void TextureFile::WriteToFile(const Texture& texture, const std::filesystem::path& path) { AssetBinary::WriteAllBytes(path, Write(texture)); } TextureFile::Texture TextureFile::ReadFromFile(const std::filesystem::path& path) { return Read(AssetBinary::ReadAllBytes(path)); } std::optional TextureFile::ReadVersionFromFile(const std::filesystem::path& path) { return AssetBinary::ReadVersion(path, Magic); }