new input system
This commit is contained in:
parent
b3db40ebec
commit
ac2eb7fb0a
31 changed files with 3292 additions and 781 deletions
189
examples/Decompression/main.cpp
Normal file
189
examples/Decompression/main.cpp
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
// End-to-end demo of GPU asset decompression via VK_EXT_memory_decompression.
|
||||
//
|
||||
// Walks the full compressed-asset pipeline:
|
||||
// 1. Build a procedural cube + checkerboard texture in memory.
|
||||
// 2. SaveCompressed → on-disk .cmesh / .ctex (GDeflate streams).
|
||||
// 3. LoadCompressed* → CompressedMeshAsset / CompressedTextureAsset.
|
||||
// 4. CPU verification: blob → DecompressCPU → byte-equal to source.
|
||||
// 5. GPU: Mesh::Build(compressedMesh, cmd) and ImageVulkan::Update(compressedTex, cmd, layout).
|
||||
// Picks the GPU path on NVIDIA (extension supported) or the CPU fallback elsewhere.
|
||||
//
|
||||
// No rendering — the goal is to exercise the asset pipeline and prove the
|
||||
// new APIs round-trip. Validation layers are enabled by Crafter.Graphics in
|
||||
// debug builds, so any VUID violation surfaces during FinishInit().
|
||||
|
||||
#include "vulkan/vulkan.h"
|
||||
|
||||
import Crafter.Graphics;
|
||||
import Crafter.Asset;
|
||||
import Crafter.Math;
|
||||
import std;
|
||||
|
||||
using namespace Crafter;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
// Procedural unit cube centered at origin, scaled so it's visible if the
|
||||
// example is ever wired into a renderer. 8 unique positions, 36 indices.
|
||||
MeshAsset<void> MakeCubeMesh() {
|
||||
MeshAsset<void> mesh;
|
||||
mesh.vertexes = {
|
||||
{-50.f, -50.f, -50.f}, { 50.f, -50.f, -50.f},
|
||||
{ 50.f, 50.f, -50.f}, {-50.f, 50.f, -50.f},
|
||||
{-50.f, -50.f, 50.f}, { 50.f, -50.f, 50.f},
|
||||
{ 50.f, 50.f, 50.f}, {-50.f, 50.f, 50.f},
|
||||
};
|
||||
mesh.indexes = {
|
||||
// -Z
|
||||
0, 1, 2, 0, 2, 3,
|
||||
// +Z
|
||||
4, 6, 5, 4, 7, 6,
|
||||
// -X
|
||||
0, 3, 7, 0, 7, 4,
|
||||
// +X
|
||||
1, 5, 6, 1, 6, 2,
|
||||
// -Y
|
||||
0, 4, 5, 0, 5, 1,
|
||||
// +Y
|
||||
3, 2, 6, 3, 6, 7,
|
||||
};
|
||||
return mesh;
|
||||
}
|
||||
|
||||
struct RGBA8 { std::uint8_t r, g, b, a; };
|
||||
|
||||
// 256×256 checkerboard with smooth radial fade — compressible enough to make
|
||||
// the GDeflate ratio interesting, structured enough that the demo is
|
||||
// meaningful.
|
||||
TextureAsset<RGBA8> MakeCheckerboard() {
|
||||
TextureAsset<RGBA8> tex;
|
||||
tex.sizeX = 256;
|
||||
tex.sizeY = 256;
|
||||
tex.opaque = OpaqueType::FullyOpaque;
|
||||
tex.pixels.resize(tex.sizeX * tex.sizeY);
|
||||
for (std::uint32_t y = 0; y < tex.sizeY; ++y) {
|
||||
for (std::uint32_t x = 0; x < tex.sizeX; ++x) {
|
||||
bool checker = ((x / 32) ^ (y / 32)) & 1;
|
||||
float dx = float(x) - 128.0f;
|
||||
float dy = float(y) - 128.0f;
|
||||
float d = std::sqrt(dx * dx + dy * dy) / 180.0f;
|
||||
float fade = std::clamp(1.0f - d, 0.0f, 1.0f);
|
||||
std::uint8_t base = checker ? 220 : 40;
|
||||
std::uint8_t lit = static_cast<std::uint8_t>(base * fade + 16);
|
||||
tex.pixels[y * tex.sizeX + x] = RGBA8{lit, lit, lit, 255};
|
||||
}
|
||||
}
|
||||
return tex;
|
||||
}
|
||||
|
||||
double Ratio(std::size_t a, std::size_t b) {
|
||||
return b == 0 ? 0.0 : 100.0 * double(a) / double(b);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const fs::path meshPath = "cube.cmesh";
|
||||
const fs::path texPath = "checker.ctex";
|
||||
|
||||
// ── 1. Build procedural assets ─────────────────────────────────────
|
||||
MeshAsset<void> srcMesh = MakeCubeMesh();
|
||||
TextureAsset<RGBA8> srcTex = MakeCheckerboard();
|
||||
|
||||
const std::size_t srcMeshBytes =
|
||||
srcMesh.vertexes.size() * sizeof(srcMesh.vertexes[0])
|
||||
+ srcMesh.indexes.size() * sizeof(srcMesh.indexes[0]);
|
||||
const std::size_t srcTexBytes =
|
||||
srcTex.pixels.size() * sizeof(RGBA8);
|
||||
|
||||
std::println("Procedural cube: {} vertices, {} indices ({} bytes)",
|
||||
srcMesh.vertexes.size(), srcMesh.indexes.size(), srcMeshBytes);
|
||||
std::println("Procedural checker: {}x{} RGBA8 ({} bytes)",
|
||||
srcTex.sizeX, srcTex.sizeY, srcTexBytes);
|
||||
|
||||
// ── 2. SaveCompressed ──────────────────────────────────────────────
|
||||
srcMesh.SaveCompressed(meshPath);
|
||||
srcTex.SaveCompressed(texPath);
|
||||
|
||||
const std::size_t meshFileSize = fs::file_size(meshPath);
|
||||
const std::size_t texFileSize = fs::file_size(texPath);
|
||||
std::println("Saved {}: {} bytes ({:.1f}% of raw)",
|
||||
meshPath.string(), meshFileSize, Ratio(meshFileSize, srcMeshBytes));
|
||||
std::println("Saved {}: {} bytes ({:.1f}% of raw)",
|
||||
texPath.string(), texFileSize, Ratio(texFileSize, srcTexBytes));
|
||||
|
||||
// ── 3. LoadCompressed ──────────────────────────────────────────────
|
||||
CompressedMeshAsset loadedMesh = LoadCompressedMesh(meshPath);
|
||||
CompressedTextureAsset loadedTex = LoadCompressedTexture(texPath);
|
||||
if (loadedMesh.vertexCount != srcMesh.vertexes.size()
|
||||
|| loadedMesh.indexCount != srcMesh.indexes.size()) {
|
||||
std::println(std::cerr,"[FAIL] mesh header mismatch after LoadCompressedMesh");
|
||||
return 1;
|
||||
}
|
||||
if (loadedTex.sizeX != srcTex.sizeX || loadedTex.sizeY != srcTex.sizeY) {
|
||||
std::println(std::cerr,"[FAIL] texture header mismatch after LoadCompressedTexture");
|
||||
return 1;
|
||||
}
|
||||
std::println("Loaded headers OK.");
|
||||
|
||||
// ── 4. CPU roundtrip verification ──────────────────────────────────
|
||||
{
|
||||
std::vector<Vector<float, 3, 3>> v(loadedMesh.vertexCount);
|
||||
std::vector<std::uint32_t> i(loadedMesh.indexCount);
|
||||
std::array<std::span<std::byte>, 3> outputs = {
|
||||
std::as_writable_bytes(std::span(v)),
|
||||
std::as_writable_bytes(std::span(i)),
|
||||
std::span<std::byte>{},
|
||||
};
|
||||
Compression::DecompressCPU(loadedMesh.blob,
|
||||
std::span(outputs).first(loadedMesh.blob.regions.size()));
|
||||
if (v != srcMesh.vertexes || i != srcMesh.indexes) {
|
||||
std::println(std::cerr,"[FAIL] CPU mesh decompress != source");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
{
|
||||
std::vector<RGBA8> p(loadedTex.sizeX * loadedTex.sizeY);
|
||||
std::array<std::span<std::byte>, 1> outputs = {
|
||||
std::as_writable_bytes(std::span(p)),
|
||||
};
|
||||
Compression::DecompressCPU(loadedTex.blob, outputs);
|
||||
if (std::memcmp(p.data(), srcTex.pixels.data(), srcTexBytes) != 0) {
|
||||
std::println(std::cerr,"[FAIL] CPU texture decompress != source");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
std::println("CPU roundtrip OK (mesh + texture decode byte-equal).");
|
||||
|
||||
// ── 5. GPU path via Mesh::Build / ImageVulkan::Update ──────────────
|
||||
Device::Initialize();
|
||||
Window window(800, 600, "Decompression");
|
||||
|
||||
std::println("VK_EXT_memory_decompression: {}",
|
||||
Device::memoryDecompressionSupported ? "AVAILABLE → GPU path" : "absent → CPU fallback");
|
||||
|
||||
VkCommandBuffer cmd = window.StartInit();
|
||||
|
||||
DescriptorHeapVulkan heap;
|
||||
heap.Initialize(/*images*/ 1, /*buffers*/ 1, /*samplers*/ 0);
|
||||
window.descriptorHeap = &heap;
|
||||
|
||||
// Mesh cubeMesh;
|
||||
// cubeMesh.Build(loadedMesh, cmd);
|
||||
|
||||
ImageVulkan<RGBA8> checkerImage;
|
||||
checkerImage.Create(
|
||||
loadedTex.sizeX, loadedTex.sizeY, /*mipLevels*/ 1, cmd,
|
||||
VK_FORMAT_R8G8B8A8_UNORM,
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
checkerImage.Update(loadedTex, cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
|
||||
window.FinishInit();
|
||||
std::println("GPU init submit + wait completed without validation errors.");
|
||||
//std::println("BLAS device address: 0x{:x}", cubeMesh.blasAddr);
|
||||
|
||||
// Cleanup happens via Window/Device dtors when they go out of scope.
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue