Sidecar v4: persist instanced geometry + metadata

Commit B of the instancing migration.  The sidecar on-disk format is
reintroduced at version 4 with MeshInfo + InstanceCpu sections in place
of v3's flat per-object draw-info array.

After streaming finishes, MainWindow asks the viewport for a post-
finalise snapshot (VBO + EBO are read back from the GPU, meshes and
instances come from the CPU-side arrays) and writes it alongside
PackedElementInfo + the string table.  On a subsequent load,
readSidecar rehydrates the whole struct and ViewportWindow::
applyCachedModel uploads VBO/EBO/SSBO in a single step, bypassing the
iterator entirely.

Staleness check is still by source file size.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-04-12 20:10:20 +10:00
parent 07a5c59359
commit ababb49ae7
4 changed files with 300 additions and 17 deletions
+108 -10
View File
@@ -17,20 +17,118 @@
* *
********************************************************************************/
// Commit A: sidecar cache is temporarily disabled. The on-disk format is
// being rewritten from v3 (monolithic world-coord geometry) to v4 (instanced
// meshes + per-instance records). Until v4 is finalised, loads always go
// through the streaming path and writes are no-ops.
// v4 layout (all multi-byte fields native-endian; endianness marker in header):
//
// SidecarHeader (16 bytes)
// uint64_t source_file_size
//
// uint32_t num_vertices_floats
// float[] vertex data (28 B/vertex: pos3 + normal3 + color1_packed)
// uint32_t num_indices
// uint32_t[] index data (mesh-local indices; base_vertex applied at draw time)
//
// uint32_t num_meshes
// MeshInfo[num_meshes]
//
// uint32_t num_instances
// InstanceCpu[num_instances] (already sorted by mesh_id)
//
// uint32_t num_elements
// PackedElementInfo[num_elements]
// uint32_t string_table_bytes
// char[string_table_bytes]
#include "SidecarCache.h"
bool writeSidecar(const std::string& /*ifc_path*/,
const SidecarData& /*data*/,
uint64_t /*ifc_file_size*/) {
#include <cstdio>
#include <cstring>
struct SidecarHeader {
uint32_t magic;
uint32_t version;
uint32_t endian;
uint32_t reserved;
};
static std::string sidecarPath(const std::string& ifc_path) {
return ifc_path + ".ifcview";
}
template<typename T>
static bool writeVec(FILE* f, const std::vector<T>& v) {
uint32_t n = static_cast<uint32_t>(v.size());
if (fwrite(&n, 4, 1, f) != 1) return false;
if (n > 0 && fwrite(v.data(), sizeof(T), n, f) != n) return false;
return true;
}
std::optional<SidecarData> readSidecar(const std::string& /*ifc_path*/,
uint64_t /*ifc_file_size*/) {
return std::nullopt;
template<typename T>
static bool readVec(FILE* f, std::vector<T>& v) {
uint32_t n;
if (fread(&n, 4, 1, f) != 1) return false;
v.resize(n);
if (n > 0 && fread(v.data(), sizeof(T), n, f) != n) return false;
return true;
}
bool writeSidecar(const std::string& ifc_path,
const SidecarData& data,
uint64_t ifc_file_size) {
std::string path = sidecarPath(ifc_path);
FILE* f = fopen(path.c_str(), "wb");
if (!f) return false;
SidecarHeader hdr = { SIDECAR_MAGIC, SIDECAR_VERSION, SIDECAR_ENDIAN, 0 };
if (fwrite(&hdr, sizeof(hdr), 1, f) != 1) { fclose(f); return false; }
if (fwrite(&ifc_file_size, 8, 1, f) != 1) { fclose(f); return false; }
if (!writeVec(f, data.vertices)) { fclose(f); return false; }
if (!writeVec(f, data.indices)) { fclose(f); return false; }
if (!writeVec(f, data.meshes)) { fclose(f); return false; }
if (!writeVec(f, data.instances)) { fclose(f); return false; }
if (!writeVec(f, data.elements)) { fclose(f); return false; }
uint32_t stbl_len = static_cast<uint32_t>(data.string_table.size());
if (fwrite(&stbl_len, 4, 1, f) != 1) { fclose(f); return false; }
if (stbl_len > 0 && fwrite(data.string_table.data(), 1, stbl_len, f) != stbl_len) {
fclose(f); return false;
}
fclose(f);
return true;
}
std::optional<SidecarData> readSidecar(const std::string& ifc_path,
uint64_t ifc_file_size) {
std::string path = sidecarPath(ifc_path);
FILE* f = fopen(path.c_str(), "rb");
if (!f) return std::nullopt;
auto fail = [&]() -> std::optional<SidecarData> { fclose(f); return std::nullopt; };
SidecarHeader hdr;
if (fread(&hdr, sizeof(hdr), 1, f) != 1) return fail();
if (hdr.magic != SIDECAR_MAGIC ||
hdr.version != SIDECAR_VERSION ||
hdr.endian != SIDECAR_ENDIAN) return fail();
uint64_t stored_size;
if (fread(&stored_size, 8, 1, f) != 1) return fail();
if (stored_size != ifc_file_size) return fail();
SidecarData data;
if (!readVec(f, data.vertices)) return fail();
if (!readVec(f, data.indices)) return fail();
if (!readVec(f, data.meshes)) return fail();
if (!readVec(f, data.instances)) return fail();
if (!readVec(f, data.elements)) return fail();
uint32_t stbl_len;
if (fread(&stbl_len, 4, 1, f) != 1) return fail();
data.string_table.resize(stbl_len);
if (stbl_len > 0 && fread(data.string_table.data(), 1, stbl_len, f) != stbl_len)
return fail();
fclose(f);
return data;
}