asset compression
This commit is contained in:
parent
b9b9ecb84c
commit
30a283c1b3
57 changed files with 13237 additions and 8 deletions
59
interfaces/Crafter.Asset-Compression.cppm
Normal file
59
interfaces/Crafter.Asset-Compression.cppm
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
Crafter®.Asset
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
catcrafts.net
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License version 3.0 as published by the Free Software Foundation;
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
export module Crafter.Asset:Compression;
|
||||
import std;
|
||||
|
||||
export namespace Crafter::Compression {
|
||||
// One independently-decompressable GDeflate stream inside CompressedBlob::bytes.
|
||||
// Layout matches what VK_EXT_memory_decompression's VkDecompressMemoryRegionEXT
|
||||
// expects, minus the device addresses (which the consumer fills in).
|
||||
struct RegionMeta {
|
||||
std::uint64_t srcOffset;
|
||||
std::uint64_t compressedSize;
|
||||
std::uint64_t decompressedSize;
|
||||
};
|
||||
|
||||
struct CompressedBlob {
|
||||
std::vector<std::byte> bytes;
|
||||
std::vector<RegionMeta> regions;
|
||||
|
||||
std::uint64_t TotalDecompressedSize() const noexcept {
|
||||
std::uint64_t sum = 0;
|
||||
for (const RegionMeta& r : regions) sum += r.decompressedSize;
|
||||
return sum;
|
||||
}
|
||||
};
|
||||
|
||||
// Compresses each input span as its own GDeflate tile-stream; concatenates
|
||||
// them into one byte buffer with a parallel region table. Streams are
|
||||
// independent and can be addressed individually by VkDecompressMemoryRegionEXT
|
||||
// entries on the GPU path.
|
||||
CompressedBlob CompressStreams(std::span<const std::span<const std::byte>> streams);
|
||||
|
||||
// CPU fallback decoder. outputs.size() must equal blob.regions.size();
|
||||
// outputs[i].size() must equal blob.regions[i].decompressedSize.
|
||||
void DecompressCPU(const CompressedBlob& blob, std::span<const std::span<std::byte>> outputs);
|
||||
|
||||
// Length-prefixed serialization of a CompressedBlob. Used by per-asset
|
||||
// SaveCompressed/LoadCompressed implementations after they've written
|
||||
// their own type-specific header.
|
||||
void WriteBlob(std::ostream& file, const CompressedBlob& blob);
|
||||
CompressedBlob ReadBlob(std::istream& file);
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
*/
|
||||
|
||||
export module Crafter.Asset:Mesh;
|
||||
import :Compression;
|
||||
import Crafter.Math;
|
||||
import std;
|
||||
namespace fs = std::filesystem;
|
||||
|
|
@ -35,6 +36,44 @@ export namespace Crafter {
|
|||
Vector<float, 2, 0> uv;
|
||||
};
|
||||
|
||||
// GDeflate-compressed counterpart of MeshAsset<T>::Save output. Three
|
||||
// regions: [vertex, index, data]. dataCount==0 leaves the data region
|
||||
// empty (zero compressedSize/decompressedSize). dataStride records sizeof(T)
|
||||
// at compress time so consumers can validate.
|
||||
struct CompressedMeshAsset {
|
||||
std::uint32_t vertexCount = 0;
|
||||
std::uint32_t indexCount = 0;
|
||||
std::uint32_t dataCount = 0;
|
||||
std::uint32_t dataStride = 0;
|
||||
Compression::CompressedBlob blob;
|
||||
};
|
||||
|
||||
namespace MeshAssetFormat {
|
||||
inline constexpr char magic[4] = {'C', 'G', 'D', 'M'};
|
||||
inline constexpr std::uint32_t version = 1;
|
||||
}
|
||||
|
||||
inline CompressedMeshAsset LoadCompressedMesh(fs::path path) {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
char magic[4];
|
||||
file.read(magic, 4);
|
||||
if (std::memcmp(magic, MeshAssetFormat::magic, 4) != 0) {
|
||||
throw std::runtime_error("LoadCompressedMesh: bad magic on " + path.string());
|
||||
}
|
||||
std::uint32_t version = 0;
|
||||
file.read(reinterpret_cast<char*>(&version), sizeof(version));
|
||||
if (version != MeshAssetFormat::version) {
|
||||
throw std::runtime_error("LoadCompressedMesh: unsupported version on " + path.string());
|
||||
}
|
||||
CompressedMeshAsset out;
|
||||
file.read(reinterpret_cast<char*>(&out.vertexCount), sizeof(out.vertexCount));
|
||||
file.read(reinterpret_cast<char*>(&out.indexCount), sizeof(out.indexCount));
|
||||
file.read(reinterpret_cast<char*>(&out.dataCount), sizeof(out.dataCount));
|
||||
file.read(reinterpret_cast<char*>(&out.dataStride), sizeof(out.dataStride));
|
||||
out.blob = Compression::ReadBlob(file);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct MeshAsset {
|
||||
std::vector<Vector<float, 3, 3>> vertexes;
|
||||
|
|
@ -57,6 +96,29 @@ export namespace Crafter {
|
|||
file.write(reinterpret_cast<char*>(datas.data()), dataCount * sizeof(T));
|
||||
}
|
||||
|
||||
void SaveCompressed(fs::path path) const {
|
||||
std::array<std::span<const std::byte>, 3> streams = {
|
||||
std::as_bytes(std::span(vertexes)),
|
||||
std::as_bytes(std::span(indexes)),
|
||||
std::as_bytes(std::span(datas)),
|
||||
};
|
||||
Compression::CompressedBlob blob = Compression::CompressStreams(streams);
|
||||
|
||||
std::ofstream file(path, std::ios::binary);
|
||||
file.write(MeshAssetFormat::magic, 4);
|
||||
std::uint32_t version = MeshAssetFormat::version;
|
||||
std::uint32_t vc = static_cast<std::uint32_t>(vertexes.size());
|
||||
std::uint32_t ic = static_cast<std::uint32_t>(indexes.size());
|
||||
std::uint32_t dc = static_cast<std::uint32_t>(datas.size());
|
||||
std::uint32_t stride = static_cast<std::uint32_t>(sizeof(T));
|
||||
file.write(reinterpret_cast<const char*>(&version), sizeof(version));
|
||||
file.write(reinterpret_cast<const char*>(&vc), sizeof(vc));
|
||||
file.write(reinterpret_cast<const char*>(&ic), sizeof(ic));
|
||||
file.write(reinterpret_cast<const char*>(&dc), sizeof(dc));
|
||||
file.write(reinterpret_cast<const char*>(&stride), sizeof(stride));
|
||||
Compression::WriteBlob(file, blob);
|
||||
}
|
||||
|
||||
static MeshAsset<T> Load(fs::path path) {
|
||||
MeshAsset<T> mesh;
|
||||
|
||||
|
|
@ -196,6 +258,32 @@ export namespace Crafter {
|
|||
file.write(reinterpret_cast<char*>(vertexes.data()), vertexCount * sizeof(Vector<float, 3, 3>));
|
||||
file.write(reinterpret_cast<char*>(indexes.data()), indexCount * sizeof(std::uint32_t));
|
||||
}
|
||||
|
||||
void SaveCompressed(fs::path path) const {
|
||||
// Three regions to keep file format identical to the templated
|
||||
// variant; the data region is empty (skipped on the GPU path).
|
||||
std::array<std::span<const std::byte>, 3> streams = {
|
||||
std::as_bytes(std::span(vertexes)),
|
||||
std::as_bytes(std::span(indexes)),
|
||||
std::span<const std::byte>{},
|
||||
};
|
||||
Compression::CompressedBlob blob = Compression::CompressStreams(streams);
|
||||
|
||||
std::ofstream file(path, std::ios::binary);
|
||||
file.write(MeshAssetFormat::magic, 4);
|
||||
std::uint32_t version = MeshAssetFormat::version;
|
||||
std::uint32_t vc = static_cast<std::uint32_t>(vertexes.size());
|
||||
std::uint32_t ic = static_cast<std::uint32_t>(indexes.size());
|
||||
std::uint32_t dc = 0;
|
||||
std::uint32_t stride = 0;
|
||||
file.write(reinterpret_cast<const char*>(&version), sizeof(version));
|
||||
file.write(reinterpret_cast<const char*>(&vc), sizeof(vc));
|
||||
file.write(reinterpret_cast<const char*>(&ic), sizeof(ic));
|
||||
file.write(reinterpret_cast<const char*>(&dc), sizeof(dc));
|
||||
file.write(reinterpret_cast<const char*>(&stride), sizeof(stride));
|
||||
Compression::WriteBlob(file, blob);
|
||||
}
|
||||
|
||||
static MeshAsset<void> Load(fs::path path) {
|
||||
MeshAsset<void> mesh;
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ module;
|
|||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "../lib/stb_image.h"
|
||||
export module Crafter.Asset:Texture;
|
||||
import :Compression;
|
||||
import std;
|
||||
import Crafter.Math;
|
||||
namespace fs = std::filesystem;
|
||||
|
|
@ -38,6 +39,42 @@ export namespace Crafter {
|
|||
OpaqueType opaque;
|
||||
};
|
||||
|
||||
// GDeflate-compressed counterpart of TextureAsset<T>::Save output.
|
||||
// Single-region blob: the pixel array as one stream.
|
||||
struct CompressedTextureAsset {
|
||||
std::uint16_t sizeX = 0;
|
||||
std::uint16_t sizeY = 0;
|
||||
OpaqueType opaque = OpaqueType::FullyOpaque;
|
||||
std::uint32_t pixelStride = 0;
|
||||
Compression::CompressedBlob blob;
|
||||
};
|
||||
|
||||
namespace TextureAssetFormat {
|
||||
inline constexpr char magic[4] = {'C', 'G', 'D', 'T'};
|
||||
inline constexpr std::uint32_t version = 1;
|
||||
}
|
||||
|
||||
inline CompressedTextureAsset LoadCompressedTexture(fs::path path) {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
char magic[4];
|
||||
file.read(magic, 4);
|
||||
if (std::memcmp(magic, TextureAssetFormat::magic, 4) != 0) {
|
||||
throw std::runtime_error("LoadCompressedTexture: bad magic on " + path.string());
|
||||
}
|
||||
std::uint32_t version = 0;
|
||||
file.read(reinterpret_cast<char*>(&version), sizeof(version));
|
||||
if (version != TextureAssetFormat::version) {
|
||||
throw std::runtime_error("LoadCompressedTexture: unsupported version on " + path.string());
|
||||
}
|
||||
CompressedTextureAsset out;
|
||||
file.read(reinterpret_cast<char*>(&out.sizeX), sizeof(out.sizeX));
|
||||
file.read(reinterpret_cast<char*>(&out.sizeY), sizeof(out.sizeY));
|
||||
file.read(reinterpret_cast<char*>(&out.opaque), sizeof(out.opaque));
|
||||
file.read(reinterpret_cast<char*>(&out.pixelStride), sizeof(out.pixelStride));
|
||||
out.blob = Compression::ReadBlob(file);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct TextureAsset {
|
||||
std::uint16_t sizeX;
|
||||
|
|
@ -54,6 +91,24 @@ export namespace Crafter {
|
|||
file.write(reinterpret_cast<char*>(pixels.data()), pixels.size() * sizeof(T));
|
||||
}
|
||||
|
||||
void SaveCompressed(fs::path path) const {
|
||||
std::array<std::span<const std::byte>, 1> streams = {
|
||||
std::as_bytes(std::span(pixels)),
|
||||
};
|
||||
Compression::CompressedBlob blob = Compression::CompressStreams(streams);
|
||||
|
||||
std::ofstream file(path, std::ios::binary);
|
||||
file.write(TextureAssetFormat::magic, 4);
|
||||
std::uint32_t version = TextureAssetFormat::version;
|
||||
std::uint32_t stride = static_cast<std::uint32_t>(sizeof(T));
|
||||
file.write(reinterpret_cast<const char*>(&version), sizeof(version));
|
||||
file.write(reinterpret_cast<const char*>(&sizeX), sizeof(sizeX));
|
||||
file.write(reinterpret_cast<const char*>(&sizeY), sizeof(sizeY));
|
||||
file.write(reinterpret_cast<const char*>(&opaque), sizeof(opaque));
|
||||
file.write(reinterpret_cast<const char*>(&stride), sizeof(stride));
|
||||
Compression::WriteBlob(file, blob);
|
||||
}
|
||||
|
||||
static TextureAsset<T> Load(fs::path path) {
|
||||
TextureAsset<T> tex;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,5 +19,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
|
||||
|
||||
export module Crafter.Asset;
|
||||
export import :Compression;
|
||||
export import :Mesh;
|
||||
export import :Texture;
|
||||
Loading…
Add table
Add a link
Reference in a new issue