106 lines
3.6 KiB
C++
106 lines
3.6 KiB
C++
#include <destrum/Assets/TextureFile.h>
|
|
|
|
#include <glm/gtc/packing.hpp>
|
|
#include <glm/vec4.hpp>
|
|
|
|
#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<const float> 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<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) {
|
|
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<std::uint32_t>(texture.format));
|
|
writer.WriteArray(texture.pixels);
|
|
return writer.TakeBytes();
|
|
}
|
|
|
|
TextureFile::Texture TextureFile::Read(std::span<const std::uint8_t> bytes) {
|
|
BinaryReader reader{bytes};
|
|
reader.ReadHeader(Magic, Version, Kind);
|
|
|
|
Texture texture;
|
|
texture.width = reader.Read<std::uint32_t>();
|
|
texture.height = reader.Read<std::uint32_t>();
|
|
texture.format = static_cast<PixelFormat>(reader.Read<std::uint32_t>());
|
|
texture.pixels = reader.ReadArray<std::uint8_t>();
|
|
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<std::uint32_t> TextureFile::ReadVersionFromFile(const std::filesystem::path& path) {
|
|
return AssetBinary::ReadVersion(path, Magic);
|
|
}
|