Improve viewer variable names

Rename short local variables and parameters in the viewer loading, sidecar, and BonsaiViewer command paths to make their responsibilities clearer.\n\nGenerated with the assistance of an AI coding tool.
This commit is contained in:
Dion Moult
2026-07-03 08:38:57 +10:00
parent f1d97ac5aa
commit 8dfe00cdf8
27 changed files with 785 additions and 727 deletions
+56 -49
View File
@@ -41,8 +41,8 @@ void BufferPool::configure(WGPUInstance instance, WGPUDevice device,
}
void BufferPool::destroy() {
for (auto& sp : sub_pools_) {
if (sp.buffer) wgpuBufferRelease(sp.buffer);
for (auto& sub_pool : sub_pools_) {
if (sub_pool.buffer) wgpuBufferRelease(sub_pool.buffer);
}
sub_pools_.clear();
device_ = nullptr;
@@ -140,7 +140,7 @@ bool BufferPool::addSubBuffer() {
WGPUBuffer buf = wgpuDeviceCreateBuffer(device_, &desc);
struct PopResult { bool done = false; bool error = false; };
auto pop = [&](PopResult& pr) {
auto pop = [&](PopResult& pop_result) {
WGPUPopErrorScopeCallbackInfo pcb = {};
pcb.mode = WGPUCallbackMode_AllowProcessEvents;
pcb.callback = [](WGPUPopErrorScopeStatus, WGPUErrorType type,
@@ -149,9 +149,9 @@ bool BufferPool::addSubBuffer() {
p->done = true;
p->error = (type != WGPUErrorType_NoError);
};
pcb.userdata1 = ≺
pcb.userdata1 = &pop_result;
wgpuDevicePopErrorScope(device_, pcb);
while (!pr.done) wgpuInstanceProcessEvents(instance_);
while (!pop_result.done) wgpuInstanceProcessEvents(instance_);
};
PopResult oom_pop, validation_pop;
pop(oom_pop);
@@ -205,11 +205,12 @@ void BufferPool::resolveProvisionalGrowth(bool failed) {
(unsigned long long)(total_capacity_bytes() / (1024 * 1024)),
sub_pools_.size());
} else {
sub_pools_[i].provisional = false;
SubPool& sub_pool = sub_pools_[i];
sub_pool.provisional = false;
std::fprintf(stderr,
"[wgpu pool] added sub-buffer %zu (%llu MB); pool total now %llu MB\n",
i,
(unsigned long long)(sub_pools_[i].capacity / (1024 * 1024)),
(unsigned long long)(sub_pool.capacity / (1024 * 1024)),
(unsigned long long)(total_capacity_bytes() / (1024 * 1024)));
}
return;
@@ -225,34 +226,34 @@ BufferPool::Slice BufferPool::alloc(uint64_t size, uint64_t align) {
// adding another sub-buffer and retry once.
for (int attempt = 0; attempt < 2; ++attempt) {
for (size_t sp_idx = 0; sp_idx < sub_pools_.size(); ++sp_idx) {
SubPool& sp = sub_pools_[sp_idx];
SubPool& sub_pool = sub_pools_[sp_idx];
// Web: never allocate out of a sub-buffer still awaiting OOM
// validation — its handle may be a Dawn error buffer.
if (sp.provisional) continue;
for (size_t i = 0; i < sp.free_ranges.size(); ++i) {
const FreeRange& r = sp.free_ranges[i];
const uint64_t aligned = (r.offset + (align - 1)) & ~(align - 1);
const uint64_t pad = aligned - r.offset;
if (pad >= r.size) continue;
if (size > r.size - pad) continue;
if (sub_pool.provisional) continue;
for (size_t i = 0; i < sub_pool.free_ranges.size(); ++i) {
const FreeRange& free_range = sub_pool.free_ranges[i];
const uint64_t aligned = (free_range.offset + (align - 1)) & ~(align - 1);
const uint64_t alignment_padding = aligned - free_range.offset;
if (alignment_padding >= free_range.size) continue;
if (size > free_range.size - alignment_padding) continue;
const uint64_t post_off = aligned + size;
const uint64_t post_size = (r.offset + r.size) - post_off;
const uint64_t post_size = (free_range.offset + free_range.size) - post_off;
if (pad == 0 && post_size == 0) {
sp.free_ranges.erase(sp.free_ranges.begin() + i);
} else if (pad == 0) {
sp.free_ranges[i] = {post_off, post_size};
if (alignment_padding == 0 && post_size == 0) {
sub_pool.free_ranges.erase(sub_pool.free_ranges.begin() + i);
} else if (alignment_padding == 0) {
sub_pool.free_ranges[i] = {post_off, post_size};
} else if (post_size == 0) {
sp.free_ranges[i] = {r.offset, pad};
sub_pool.free_ranges[i] = {free_range.offset, alignment_padding};
} else {
sp.free_ranges[i] = {r.offset, pad};
sp.free_ranges.insert(sp.free_ranges.begin() + i + 1,
sub_pool.free_ranges[i] = {free_range.offset, alignment_padding};
sub_pool.free_ranges.insert(sub_pool.free_ranges.begin() + i + 1,
{post_off, post_size});
}
sp.used += size;
out.buffer = sp.buffer;
sub_pool.used += size;
out.buffer = sub_pool.buffer;
out.offset = aligned;
out.size = size;
out.sub_idx = int(sp_idx);
@@ -270,50 +271,56 @@ BufferPool::Slice BufferPool::alloc(uint64_t size, uint64_t align) {
void BufferPool::free(const Slice& s) {
if (!s.valid()) return;
if (s.sub_idx < 0 || size_t(s.sub_idx) >= sub_pools_.size()) return;
SubPool& sp = sub_pools_[size_t(s.sub_idx)];
assert(s.offset + s.size <= sp.capacity);
SubPool& sub_pool = sub_pools_[size_t(s.sub_idx)];
assert(s.offset + s.size <= sub_pool.capacity);
size_t i = 0;
while (i < sp.free_ranges.size() && sp.free_ranges[i].offset < s.offset) ++i;
sp.free_ranges.insert(sp.free_ranges.begin() + i, {s.offset, s.size});
sp.used -= s.size;
while (i < sub_pool.free_ranges.size() && sub_pool.free_ranges[i].offset < s.offset) ++i;
sub_pool.free_ranges.insert(sub_pool.free_ranges.begin() + i, {s.offset, s.size});
sub_pool.used -= s.size;
if (i + 1 < sp.free_ranges.size()
&& sp.free_ranges[i].offset + sp.free_ranges[i].size == sp.free_ranges[i + 1].offset) {
sp.free_ranges[i].size += sp.free_ranges[i + 1].size;
sp.free_ranges.erase(sp.free_ranges.begin() + i + 1);
if (i + 1 < sub_pool.free_ranges.size()
&& sub_pool.free_ranges[i].offset + sub_pool.free_ranges[i].size
== sub_pool.free_ranges[i + 1].offset) {
sub_pool.free_ranges[i].size += sub_pool.free_ranges[i + 1].size;
sub_pool.free_ranges.erase(sub_pool.free_ranges.begin() + i + 1);
}
if (i > 0
&& sp.free_ranges[i - 1].offset + sp.free_ranges[i - 1].size == sp.free_ranges[i].offset) {
sp.free_ranges[i - 1].size += sp.free_ranges[i].size;
sp.free_ranges.erase(sp.free_ranges.begin() + i);
&& sub_pool.free_ranges[i - 1].offset + sub_pool.free_ranges[i - 1].size
== sub_pool.free_ranges[i].offset) {
sub_pool.free_ranges[i - 1].size += sub_pool.free_ranges[i].size;
sub_pool.free_ranges.erase(sub_pool.free_ranges.begin() + i);
}
}
uint64_t BufferPool::total_capacity_bytes() const {
uint64_t s = 0;
uint64_t total_capacity = 0;
// Skip provisional sub-pools (web, awaiting OOM validation) — their
// capacity isn't usable yet, so counting it would mislead the
// evictor's "is there room?" heuristics.
for (const auto& sp : sub_pools_) if (!sp.provisional) s += sp.capacity;
return s;
for (const auto& sub_pool : sub_pools_) {
if (!sub_pool.provisional) total_capacity += sub_pool.capacity;
}
return total_capacity;
}
uint64_t BufferPool::total_used_bytes() const {
uint64_t s = 0;
for (const auto& sp : sub_pools_) if (!sp.provisional) s += sp.used;
return s;
uint64_t total_used = 0;
for (const auto& sub_pool : sub_pools_) {
if (!sub_pool.provisional) total_used += sub_pool.used;
}
return total_used;
}
uint64_t BufferPool::largest_free_run_bytes() const {
uint64_t m = 0;
for (const auto& sp : sub_pools_) {
if (sp.provisional) continue;
for (const auto& r : sp.free_ranges) {
if (r.size > m) m = r.size;
uint64_t largest_free_run = 0;
for (const auto& sub_pool : sub_pools_) {
if (sub_pool.provisional) continue;
for (const auto& free_range : sub_pool.free_ranges) {
if (free_range.size > largest_free_run) largest_free_run = free_range.size;
}
}
return m;
return largest_free_run;
}
void BufferPool::addSubBufferForTesting(WGPUBuffer fake_buffer, uint64_t capacity) {
+45 -43
View File
@@ -42,29 +42,29 @@ struct MaterialInfo {
};
static MaterialInfo materialFromStyle(const ifcopenshell::geometry::taxonomy::style::ptr& style) {
MaterialInfo m;
if (!style) return m;
MaterialInfo material;
if (!style) return material;
const auto& color = style->get_color();
if (color) {
m.r = static_cast<float>(color.r());
m.g = static_cast<float>(color.g());
m.b = static_cast<float>(color.b());
material.r = static_cast<float>(color.r());
material.g = static_cast<float>(color.g());
material.b = static_cast<float>(color.b());
}
if (!std::isnan(style->transparency)) {
m.a = 1.0f - static_cast<float>(style->transparency);
material.a = 1.0f - static_cast<float>(style->transparency);
}
return m;
return material;
}
static inline uint32_t packRGBA8(const MaterialInfo& m) {
auto to_byte = [](float v) -> uint32_t {
float c = std::clamp(v, 0.0f, 1.0f);
return static_cast<uint32_t>(c * 255.0f + 0.5f);
static inline uint32_t packRGBA8(const MaterialInfo& material) {
auto to_byte = [](float channel_value) -> uint32_t {
float clamped_value = std::clamp(channel_value, 0.0f, 1.0f);
return static_cast<uint32_t>(clamped_value * 255.0f + 0.5f);
};
uint32_t r = to_byte(m.r);
uint32_t g = to_byte(m.g);
uint32_t b = to_byte(m.b);
uint32_t a = to_byte(m.a);
uint32_t r = to_byte(material.r);
uint32_t g = to_byte(material.g);
uint32_t b = to_byte(material.b);
uint32_t a = to_byte(material.a);
// Little-endian byte layout [r,g,b,a] for GL_UNSIGNED_BYTE * 4 normalized.
return r | (g << 8) | (b << 16) | (a << 24);
}
@@ -188,12 +188,12 @@ static MeshChunk buildMeshChunk(uint32_t model_id,
chunk.indices.reserve(faces.size());
// Track local AABB as we emit vertices.
float amin[3] = { std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max() };
float amax[3] = { -std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max() };
float local_aabb_min[3] = { std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max() };
float local_aabb_max[3] = { -std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max() };
auto emit_vertex = [&](uint32_t orig_idx, int mat_id) -> uint32_t {
const uint64_t key = make_key(orig_idx, mat_id);
@@ -211,9 +211,12 @@ static MeshChunk buildMeshChunk(uint32_t model_id,
chunk.vertices.push_back(px);
chunk.vertices.push_back(py);
chunk.vertices.push_back(pz);
if (px < amin[0]) amin[0] = px; if (px > amax[0]) amax[0] = px;
if (py < amin[1]) amin[1] = py; if (py > amax[1]) amax[1] = py;
if (pz < amin[2]) amin[2] = pz; if (pz > amax[2]) amax[2] = pz;
if (px < local_aabb_min[0]) local_aabb_min[0] = px;
if (px > local_aabb_max[0]) local_aabb_max[0] = px;
if (py < local_aabb_min[1]) local_aabb_min[1] = py;
if (py > local_aabb_max[1]) local_aabb_max[1] = py;
if (pz < local_aabb_min[2]) local_aabb_min[2] = pz;
if (pz > local_aabb_max[2]) local_aabb_max[2] = pz;
if (orig_idx * 3 + 2 < normals.size()) {
chunk.vertices.push_back(static_cast<float>(normals[orig_idx * 3 + 0]));
@@ -246,11 +249,11 @@ static MeshChunk buildMeshChunk(uint32_t model_id,
}
if (chunk.vertices.empty()) {
for (int a = 0; a < 3; ++a) amin[a] = amax[a] = 0.0f;
for (int a = 0; a < 3; ++a) local_aabb_min[a] = local_aabb_max[a] = 0.0f;
}
for (int a = 0; a < 3; ++a) {
chunk.local_aabb_min[a] = amin[a];
chunk.local_aabb_max[a] = amax[a];
chunk.local_aabb_min[a] = local_aabb_min[a];
chunk.local_aabb_max[a] = local_aabb_max[a];
}
return chunk;
}
@@ -527,7 +530,6 @@ void GeometryStreamer::run(const std::string& path, int num_threads) {
emit errorOccurred(QString("Failed to create geometry iterator: %1").arg(e.what()));
return false;
}
if (!iterator->initialize()) {
// No geometry survived this context for the remaining ids.
// Subsequent contexts will pick them up; nothing to emit.
@@ -604,15 +606,15 @@ void GeometryStreamer::run(const std::string& path, int num_threads) {
MeshChunk mesh_chunk =
buildMeshChunk(model_id_, local_mesh_id, tri_elem, offset);
MeshAabb ma;
MeshAabb mesh_aabb;
for (int a = 0; a < 3; ++a) {
ma.lmin[a] = mesh_chunk.local_aabb_min[a];
ma.lmax[a] = mesh_chunk.local_aabb_max[a];
ma.offset[a] = offset[a];
mesh_aabb.lmin[a] = mesh_chunk.local_aabb_min[a];
mesh_aabb.lmax[a] = mesh_chunk.local_aabb_max[a];
mesh_aabb.offset[a] = offset[a];
}
ma.has_offset = (offset.squaredNorm() > 0.0);
mesh_aabb.has_offset = (offset.squaredNorm() > 0.0);
if (mesh_aabbs.size() <= local_mesh_id) mesh_aabbs.resize(local_mesh_id + 1);
mesh_aabbs[local_mesh_id] = ma;
mesh_aabbs[local_mesh_id] = mesh_aabb;
if (!mesh_chunk.indices.empty()) {
emit meshReady(std::move(mesh_chunk));
}
@@ -626,11 +628,11 @@ void GeometryStreamer::run(const std::string& path, int num_threads) {
Eigen::Matrix4d mat_d =
tri_elem->transformation().data()->ccomponents();
if (mesh_aabbs[local_mesh_id].has_offset) {
const Eigen::Vector3d off(
const Eigen::Vector3d mesh_rebase_offset(
mesh_aabbs[local_mesh_id].offset[0],
mesh_aabbs[local_mesh_id].offset[1],
mesh_aabbs[local_mesh_id].offset[2]);
mat_d.block<3, 1>(0, 3) += mat_d.block<3, 3>(0, 0) * off;
mat_d.block<3, 1>(0, 3) += mat_d.block<3, 3>(0, 0) * mesh_rebase_offset;
}
InstanceChunk inst;
@@ -642,25 +644,25 @@ void GeometryStreamer::run(const std::string& path, int num_threads) {
inst.transform[i] = mat_d.data()[i];
}
const MeshAabb& ma = mesh_aabbs[local_mesh_id];
const MeshAabb& mesh_aabb = mesh_aabbs[local_mesh_id];
float mat_f[16];
for (int i = 0; i < 16; ++i) {
mat_f[i] = static_cast<float>(inst.transform[i]);
}
worldAabbFromLocal(ma.lmin, ma.lmax, mat_f,
worldAabbFromLocal(mesh_aabb.lmin, mesh_aabb.lmax, mat_f,
inst.world_aabb_min, inst.world_aabb_max);
emit instanceReady(std::move(inst));
total_shapes++;
yielded_count++;
const int p = total_count > 0
const int progress_percent = total_count > 0
? static_cast<int>((100 * yielded_count) / total_count)
: 100;
if (p != last_emitted_progress) {
last_emitted_progress = p;
progress_ = p;
emit progressChanged(p);
if (progress_percent != last_emitted_progress) {
last_emitted_progress = progress_percent;
progress_ = progress_percent;
emit progressChanged(progress_percent);
}
} while (iterator->next());
+9 -9
View File
@@ -79,16 +79,16 @@ bool findInstanceInModels(
const std::unordered_map<uint32_t, ModelGpuData>& models,
InstanceLookup& out) {
if (object_id == 0) return false;
for (const auto& [mid, m] : models) {
auto it = m.object_id_to_instance.find(object_id);
if (it == m.object_id_to_instance.end()) continue;
const uint32_t inst_idx = it->second;
if (inst_idx >= m.instances.size()) continue;
const InstanceCpu& inst = m.instances[inst_idx];
out.model_id = mid;
out.mesh_id = inst.mesh_id;
for (const auto& [model_id, model_data] : models) {
auto it = model_data.object_id_to_instance.find(object_id);
if (it == model_data.object_id_to_instance.end()) continue;
const uint32_t instance_index = it->second;
if (instance_index >= model_data.instances.size()) continue;
const InstanceCpu& instance = model_data.instances[instance_index];
out.model_id = model_id;
out.mesh_id = instance.mesh_id;
std::memcpy(out.placement_transformation,
inst.placement_transformation,
instance.placement_transformation,
sizeof(out.placement_transformation));
return true;
}
+14 -14
View File
@@ -53,10 +53,10 @@ void SidecarBuilder::onMeshReady(const MeshChunk& chunk) {
-std::numeric_limits<float>::infinity(),
-std::numeric_limits<float>::infinity() };
for (size_t i = 0; i < n_verts; ++i) {
const float* v = chunk.vertices.data() + i * INSTANCED_VERTEX_STRIDE_FLOATS;
const float* vertex = chunk.vertices.data() + i * INSTANCED_VERTEX_STRIDE_FLOATS;
for (int a = 0; a < 3; ++a) {
if (v[a] < bmin[a]) bmin[a] = v[a];
if (v[a] > bmax[a]) bmax[a] = v[a];
if (vertex[a] < bmin[a]) bmin[a] = vertex[a];
if (vertex[a] > bmax[a]) bmax[a] = vertex[a];
}
}
float extent_recip[3];
@@ -99,25 +99,25 @@ void SidecarBuilder::onMeshReady(const MeshChunk& chunk) {
}
void SidecarBuilder::onInstanceReady(const InstanceChunk& chunk) {
InstanceCpu inst;
inst.mesh_id = chunk.local_mesh_id;
inst.object_id = chunk.object_id;
inst.color_override_rgba8 = chunk.color_override_rgba8;
inst.model_id = chunk.model_id;
InstanceCpu instance;
instance.mesh_id = chunk.local_mesh_id;
instance.object_id = chunk.object_id;
instance.color_override_rgba8 = chunk.color_override_rgba8;
instance.model_id = chunk.model_id;
// The streamer's chunk.transform is the double-precision
// placement_transformation. The cached float transform/world_aabb is only
// an identity-stage baseline; applyCachedModel recomposes from placement
// against the consumer's stage matrices at load time.
std::memcpy(inst.placement_transformation, chunk.transform,
sizeof(inst.placement_transformation));
std::memcpy(instance.placement_transformation, chunk.transform,
sizeof(instance.placement_transformation));
for (int i = 0; i < 16; ++i) {
inst.transform[i] = static_cast<float>(chunk.transform[i]);
instance.transform[i] = static_cast<float>(chunk.transform[i]);
}
std::memcpy(inst.world_aabb_min, chunk.world_aabb_min, sizeof(inst.world_aabb_min));
std::memcpy(inst.world_aabb_max, chunk.world_aabb_max, sizeof(inst.world_aabb_max));
std::memcpy(instance.world_aabb_min, chunk.world_aabb_min, sizeof(instance.world_aabb_min));
std::memcpy(instance.world_aabb_max, chunk.world_aabb_max, sizeof(instance.world_aabb_max));
sidecar_data_.instances.push_back(inst);
sidecar_data_.instances.push_back(instance);
}
SidecarData SidecarBuilder::finalize(const ModelGeoref& georef,
+138 -103
View File
@@ -59,50 +59,61 @@ static constexpr int kSidecarZstdLevel = 19;
// --- In-memory serialisation (a block is built in RAM, then compressed) ------
template<typename T>
static void appendVec(std::vector<std::uint8_t>& b, const std::vector<T>& v) {
std::uint32_t n = static_cast<std::uint32_t>(v.size());
const auto* np = reinterpret_cast<const std::uint8_t*>(&n);
b.insert(b.end(), np, np + 4);
if (n > 0) {
const auto* p = reinterpret_cast<const std::uint8_t*>(v.data());
b.insert(b.end(), p, p + std::size_t(sizeof(T)) * n);
static void appendVec(std::vector<std::uint8_t>& buffer, const std::vector<T>& values) {
std::uint32_t count = static_cast<std::uint32_t>(values.size());
const auto* count_bytes = reinterpret_cast<const std::uint8_t*>(&count);
buffer.insert(buffer.end(), count_bytes, count_bytes + 4);
if (count > 0) {
const auto* value_bytes = reinterpret_cast<const std::uint8_t*>(values.data());
buffer.insert(buffer.end(), value_bytes, value_bytes + std::size_t(sizeof(T)) * count);
}
}
static void appendBytes(std::vector<std::uint8_t>& b, const void* p, std::size_t n) {
const auto* c = static_cast<const std::uint8_t*>(p);
b.insert(b.end(), c, c + n);
static void appendBytes(std::vector<std::uint8_t>& buffer, const void* data, std::size_t byte_count) {
const auto* bytes = static_cast<const std::uint8_t*>(data);
buffer.insert(buffer.end(), bytes, bytes + byte_count);
}
// Pull one chunk's geometry out of the whole-model vertex/index arrays into the
// chunk-LOCAL layout applyStreamedChunk expects: vertices of its meshes in chunk
// order, then indices as LOD0 (per mesh) followed by LOD1 (per mesh).
static void extractChunkGeometry(const SidecarData& d, const SidecarChunk& c,
static void extractChunkGeometry(const SidecarData& sidecar_data, const SidecarChunk& sidecar_chunk,
std::vector<std::uint8_t>& vbytes,
std::vector<std::uint8_t>& ibytes) {
vbytes.clear();
ibytes.clear();
const std::uint32_t end = c.first_mesh + c.mesh_count;
for (std::uint32_t mi = c.first_mesh; mi < end && mi < d.meshes.size(); ++mi) {
const MeshInfo& m = d.meshes[mi];
const std::size_t voff = m.vbo_byte_offset;
const std::size_t vn = std::size_t(m.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES;
if (voff + vn <= d.vertices.size())
vbytes.insert(vbytes.end(), d.vertices.begin() + voff,
d.vertices.begin() + voff + vn);
const std::uint32_t end = sidecar_chunk.first_mesh + sidecar_chunk.mesh_count;
for (std::uint32_t mesh_index = sidecar_chunk.first_mesh;
mesh_index < end && mesh_index < sidecar_data.meshes.size();
++mesh_index) {
const MeshInfo& mesh_info = sidecar_data.meshes[mesh_index];
const std::size_t vertex_offset = mesh_info.vbo_byte_offset;
const std::size_t vertex_byte_count =
std::size_t(mesh_info.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES;
if (vertex_offset + vertex_byte_count <= sidecar_data.vertices.size())
vbytes.insert(vbytes.end(), sidecar_data.vertices.begin() + vertex_offset,
sidecar_data.vertices.begin() + vertex_offset + vertex_byte_count);
}
auto appendIdx = [&](std::size_t first_u32, std::size_t count) {
if (first_u32 + count > d.indices.size()) return;
const auto* p = reinterpret_cast<const std::uint8_t*>(d.indices.data() + first_u32);
ibytes.insert(ibytes.end(), p, p + count * sizeof(std::uint32_t));
if (first_u32 + count > sidecar_data.indices.size()) return;
const auto* index_bytes =
reinterpret_cast<const std::uint8_t*>(sidecar_data.indices.data() + first_u32);
ibytes.insert(ibytes.end(), index_bytes, index_bytes + count * sizeof(std::uint32_t));
};
for (std::uint32_t mi = c.first_mesh; mi < end && mi < d.meshes.size(); ++mi) {
const MeshInfo& m = d.meshes[mi];
if (m.index_count) appendIdx(m.ebo_byte_offset / sizeof(std::uint32_t), m.index_count);
for (std::uint32_t mesh_index = sidecar_chunk.first_mesh;
mesh_index < end && mesh_index < sidecar_data.meshes.size();
++mesh_index) {
const MeshInfo& mesh_info = sidecar_data.meshes[mesh_index];
if (mesh_info.index_count) {
appendIdx(mesh_info.ebo_byte_offset / sizeof(std::uint32_t), mesh_info.index_count);
}
}
for (std::uint32_t mi = c.first_mesh; mi < end && mi < d.meshes.size(); ++mi) {
const MeshInfo& m = d.meshes[mi];
if (m.lod1_index_count)
appendIdx(m.lod1_ebo_byte_offset / sizeof(std::uint32_t), m.lod1_index_count);
for (std::uint32_t mesh_index = sidecar_chunk.first_mesh;
mesh_index < end && mesh_index < sidecar_data.meshes.size();
++mesh_index) {
const MeshInfo& mesh_info = sidecar_data.meshes[mesh_index];
if (mesh_info.lod1_index_count) {
appendIdx(mesh_info.lod1_ebo_byte_offset / sizeof(std::uint32_t), mesh_info.lod1_index_count);
}
}
}
#endif // !__EMSCRIPTEN__ (bake-only serialisation helpers)
@@ -118,14 +129,14 @@ struct SidecarHeader {
// foo.ifcdb -> foo.ifcview
// foo (no ext) -> foo.ifcview
static std::string sidecarPath(const std::string& ifc_path) {
std::string p = ifc_path;
while (!p.empty() && (p.back() == '/' || p.back() == '\\')) p.pop_back();
auto slash = p.find_last_of("/\\");
auto dot = p.find_last_of('.');
std::string path = ifc_path;
while (!path.empty() && (path.back() == '/' || path.back() == '\\')) path.pop_back();
auto slash = path.find_last_of("/\\");
auto dot = path.find_last_of('.');
std::string stem = (dot != std::string::npos &&
(slash == std::string::npos || dot > slash))
? p.substr(0, dot)
: p;
? path.substr(0, dot)
: path;
return stem + ".ifcview";
}
@@ -152,18 +163,18 @@ bool writeSidecar(const std::string& ifc_path, const SidecarData& data) {
FILE* f = fopen(path.c_str(), "wb");
if (!f) return false;
auto wr = [&](const void* p, std::size_t n) {
return fwrite(p, 1, n, f) == n;
auto write_bytes = [&](const void* data, std::size_t byte_count) {
return fwrite(data, 1, byte_count, f) == byte_count;
};
auto wrU64 = [&](std::uint64_t v) { return wr(&v, sizeof(v)); };
auto wrU64 = [&](std::uint64_t v) { return write_bytes(&v, sizeof(v)); };
auto wrBlock = [&](const std::vector<std::uint8_t>& raw) -> bool {
auto z = SidecarCompress::compress(raw.data(), raw.size(), kSidecarZstdLevel);
if (raw.size() > 0 && z.empty()) return false; // compress failed
return wrU64(z.size()) && wrU64(raw.size()) && (z.empty() || wr(z.data(), z.size()));
return wrU64(z.size()) && wrU64(raw.size()) && (z.empty() || write_bytes(z.data(), z.size()));
};
SidecarHeader hdr = { SIDECAR_MAGIC, SIDECAR_VERSION, SIDECAR_ENDIAN };
if (!wr(&hdr, sizeof(hdr))) { fclose(f); return false; }
if (!write_bytes(&hdr, sizeof(hdr))) { fclose(f); return false; }
// --- Geometry section: per-chunk zstd(vertex) + zstd(index) frames -------
// Offsets in the chunk TOC are relative to the geometry section start, so
@@ -174,19 +185,19 @@ bool writeSidecar(const std::string& ifc_path, const SidecarData& data) {
std::vector<SidecarChunk> chunks = data.chunks; // fill blob offsets below
std::vector<std::uint8_t> vraw, iraw;
for (auto& c : chunks) {
extractChunkGeometry(data, c, vraw, iraw);
for (auto& sidecar_chunk : chunks) {
extractChunkGeometry(data, sidecar_chunk, vraw, iraw);
auto vz = SidecarCompress::compress(vraw.data(), vraw.size(), kSidecarZstdLevel);
auto iz = SidecarCompress::compress(iraw.data(), iraw.size(), kSidecarZstdLevel);
if ((vraw.size() && vz.empty()) || (iraw.size() && iz.empty())) { fclose(f); return false; }
c.v_comp_off = std::uint64_t(ftell(f) - geom_start);
c.v_comp_size = vz.size();
c.v_raw_size = vraw.size();
if (!vz.empty() && !wr(vz.data(), vz.size())) { fclose(f); return false; }
c.i_comp_off = std::uint64_t(ftell(f) - geom_start);
c.i_comp_size = iz.size();
c.i_raw_size = iraw.size();
if (!iz.empty() && !wr(iz.data(), iz.size())) { fclose(f); return false; }
sidecar_chunk.v_comp_off = std::uint64_t(ftell(f) - geom_start);
sidecar_chunk.v_comp_size = vz.size();
sidecar_chunk.v_raw_size = vraw.size();
if (!vz.empty() && !write_bytes(vz.data(), vz.size())) { fclose(f); return false; }
sidecar_chunk.i_comp_off = std::uint64_t(ftell(f) - geom_start);
sidecar_chunk.i_comp_size = iz.size();
sidecar_chunk.i_raw_size = iraw.size();
if (!iz.empty() && !write_bytes(iz.data(), iz.size())) { fclose(f); return false; }
}
const long geom_end = ftell(f);
if (geom_start < 0 || geom_end < 0) { fclose(f); return false; }
@@ -195,23 +206,23 @@ bool writeSidecar(const std::string& ifc_path, const SidecarData& data) {
if (fseek(f, geom_end, SEEK_SET) != 0) { fclose(f); return false; }
// --- Critical metadata block (zstd): meshes, instances, georef, chunk TOC
std::vector<std::uint8_t> crit;
appendVec(crit, data.meshes);
appendVec(crit, data.instances);
appendBytes(crit, &data.has_coordinate_operation, 4);
appendBytes(crit, data.coordinate_operation_meters, sizeof(double) * 16);
appendBytes(crit, &data.project_length_to_meters, sizeof(double));
appendBytes(crit, &data.map_unit_to_meters, sizeof(double));
appendVec(crit, chunks);
if (!wrBlock(crit)) { fclose(f); return false; }
std::vector<std::uint8_t> critical_metadata;
appendVec(critical_metadata, data.meshes);
appendVec(critical_metadata, data.instances);
appendBytes(critical_metadata, &data.has_coordinate_operation, 4);
appendBytes(critical_metadata, data.coordinate_operation_meters, sizeof(double) * 16);
appendBytes(critical_metadata, &data.project_length_to_meters, sizeof(double));
appendBytes(critical_metadata, &data.map_unit_to_meters, sizeof(double));
appendVec(critical_metadata, chunks);
if (!wrBlock(critical_metadata)) { fclose(f); return false; }
// --- Deferred metadata block (zstd): element tree + string table ---------
std::vector<std::uint8_t> def;
appendVec(def, data.elements);
std::vector<std::uint8_t> deferred_metadata;
appendVec(deferred_metadata, data.elements);
std::uint32_t stbl_len = static_cast<std::uint32_t>(data.string_table.size());
appendBytes(def, &stbl_len, 4);
appendBytes(def, data.string_table.data(), stbl_len);
if (!wrBlock(def)) { fclose(f); return false; }
appendBytes(deferred_metadata, &stbl_len, 4);
appendBytes(deferred_metadata, data.string_table.data(), stbl_len);
if (!wrBlock(deferred_metadata)) { fclose(f); return false; }
fclose(f);
return true;
@@ -257,28 +268,30 @@ std::optional<SidecarData> readSidecar(const std::string& ifc_path) {
if (hdr.magic != SIDECAR_MAGIC || hdr.version != SIDECAR_VERSION ||
hdr.endian != SIDECAR_ENDIAN) return fail();
auto rd = [&](void* p, std::size_t k) { return fread(p, 1, k, f) == k; };
auto rdU64 = [&](std::uint64_t& v) { return rd(&v, sizeof(v)); };
auto read_bytes = [&](void* data, std::size_t byte_count) {
return fread(data, 1, byte_count, f) == byte_count;
};
auto rdU64 = [&](std::uint64_t& v) { return read_bytes(&v, sizeof(v)); };
std::uint64_t geom_bytes = 0;
if (!rdU64(geom_bytes)) return fail();
std::vector<std::uint8_t> geom(static_cast<std::size_t>(geom_bytes));
if (geom_bytes && !rd(geom.data(), geom.size())) return fail();
if (geom_bytes && !read_bytes(geom.data(), geom.size())) return fail();
auto readBlock = [&](std::vector<std::uint8_t>& out) -> bool {
std::uint64_t comp = 0, raw = 0;
if (!rdU64(comp) || !rdU64(raw)) return false;
std::vector<std::uint8_t> z(static_cast<std::size_t>(comp));
if (comp && !rd(z.data(), z.size())) return false;
if (comp && !read_bytes(z.data(), z.size())) return false;
out.assign(std::size_t(raw), 0);
return SidecarCompress::decompress(z.data(), z.size(), out.data(), out.size());
};
std::vector<std::uint8_t> crit, def;
if (!readBlock(crit) || !readBlock(def)) return fail();
std::vector<std::uint8_t> critical_metadata, deferred_metadata;
if (!readBlock(critical_metadata) || !readBlock(deferred_metadata)) return fail();
fclose(f);
SidecarData data;
BufReader cr{ crit.data(), crit.size() };
BufReader cr{ critical_metadata.data(), critical_metadata.size() };
if (!cr.takeVec(data.meshes)) return std::nullopt;
if (!cr.takeVec(data.instances)) return std::nullopt;
if (!cr.take(&data.has_coordinate_operation, 4)) return std::nullopt;
@@ -287,7 +300,7 @@ std::optional<SidecarData> readSidecar(const std::string& ifc_path) {
if (!cr.take(&data.map_unit_to_meters, sizeof(double))) return std::nullopt;
if (!cr.takeVec(data.chunks)) return std::nullopt;
BufReader dr{ def.data(), def.size() };
BufReader dr{ deferred_metadata.data(), deferred_metadata.size() };
if (!dr.takeVec(data.elements)) return std::nullopt;
std::uint32_t stbl_len = 0;
if (!dr.take(&stbl_len, 4)) return std::nullopt;
@@ -296,46 +309,68 @@ std::optional<SidecarData> readSidecar(const std::string& ifc_path) {
// Reconstruct the whole-model vertex/index arrays from the per-chunk blobs.
std::size_t vsize = 0, isize = 0;
for (const auto& m : data.meshes) {
for (const auto& mesh_info : data.meshes) {
vsize = std::max<std::size_t>(vsize,
std::size_t(m.vbo_byte_offset) + std::size_t(m.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES);
isize = std::max<std::size_t>(isize, m.ebo_byte_offset / sizeof(std::uint32_t) + m.index_count);
if (m.lod1_index_count)
isize = std::max<std::size_t>(isize, m.lod1_ebo_byte_offset / sizeof(std::uint32_t) + m.lod1_index_count);
std::size_t(mesh_info.vbo_byte_offset) +
std::size_t(mesh_info.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES);
isize = std::max<std::size_t>(
isize, mesh_info.ebo_byte_offset / sizeof(std::uint32_t) + mesh_info.index_count);
if (mesh_info.lod1_index_count) {
isize = std::max<std::size_t>(
isize,
mesh_info.lod1_ebo_byte_offset / sizeof(std::uint32_t) + mesh_info.lod1_index_count);
}
}
data.vertices.assign(vsize, 0);
data.indices.assign(isize, 0);
for (const auto& c : data.chunks) {
if (c.v_comp_off + c.v_comp_size > geom.size() ||
c.i_comp_off + c.i_comp_size > geom.size()) return std::nullopt;
std::vector<std::uint8_t> vraw(static_cast<std::size_t>(c.v_raw_size));
std::vector<std::uint8_t> iraw(static_cast<std::size_t>(c.i_raw_size));
if (!SidecarCompress::decompress(geom.data() + c.v_comp_off, c.v_comp_size, vraw.data(), vraw.size()) ||
!SidecarCompress::decompress(geom.data() + c.i_comp_off, c.i_comp_size, iraw.data(), iraw.size()))
for (const auto& sidecar_chunk : data.chunks) {
if (sidecar_chunk.v_comp_off + sidecar_chunk.v_comp_size > geom.size() ||
sidecar_chunk.i_comp_off + sidecar_chunk.i_comp_size > geom.size()) return std::nullopt;
std::vector<std::uint8_t> vraw(static_cast<std::size_t>(sidecar_chunk.v_raw_size));
std::vector<std::uint8_t> iraw(static_cast<std::size_t>(sidecar_chunk.i_raw_size));
if (!SidecarCompress::decompress(
geom.data() + sidecar_chunk.v_comp_off, sidecar_chunk.v_comp_size, vraw.data(), vraw.size()) ||
!SidecarCompress::decompress(
geom.data() + sidecar_chunk.i_comp_off, sidecar_chunk.i_comp_size, iraw.data(), iraw.size()))
return std::nullopt;
const auto* iu = reinterpret_cast<const std::uint32_t*>(iraw.data());
std::size_t vcur = 0, icur = 0;
const std::uint32_t end = c.first_mesh + c.mesh_count;
for (std::uint32_t mi = c.first_mesh; mi < end && mi < data.meshes.size(); ++mi) {
const MeshInfo& m = data.meshes[mi];
const std::size_t vn = std::size_t(m.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES;
if (vcur + vn <= vraw.size() && m.vbo_byte_offset + vn <= data.vertices.size())
std::memcpy(&data.vertices[m.vbo_byte_offset], vraw.data() + vcur, vn);
vcur += vn;
const std::uint32_t end = sidecar_chunk.first_mesh + sidecar_chunk.mesh_count;
for (std::uint32_t mesh_index = sidecar_chunk.first_mesh;
mesh_index < end && mesh_index < data.meshes.size();
++mesh_index) {
const MeshInfo& mesh_info = data.meshes[mesh_index];
const std::size_t vertex_byte_count =
std::size_t(mesh_info.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES;
if (vcur + vertex_byte_count <= vraw.size() &&
mesh_info.vbo_byte_offset + vertex_byte_count <= data.vertices.size()) {
std::memcpy(&data.vertices[mesh_info.vbo_byte_offset], vraw.data() + vcur, vertex_byte_count);
}
vcur += vertex_byte_count;
}
for (std::uint32_t mi = c.first_mesh; mi < end && mi < data.meshes.size(); ++mi) {
const MeshInfo& m = data.meshes[mi];
if (!m.index_count) continue;
if (icur + m.index_count <= iraw.size() / 4)
std::memcpy(&data.indices[m.ebo_byte_offset / sizeof(std::uint32_t)], iu + icur, m.index_count * 4);
icur += m.index_count;
for (std::uint32_t mesh_index = sidecar_chunk.first_mesh;
mesh_index < end && mesh_index < data.meshes.size();
++mesh_index) {
const MeshInfo& mesh_info = data.meshes[mesh_index];
if (!mesh_info.index_count) continue;
if (icur + mesh_info.index_count <= iraw.size() / 4) {
std::memcpy(&data.indices[mesh_info.ebo_byte_offset / sizeof(std::uint32_t)],
iu + icur,
mesh_info.index_count * 4);
}
icur += mesh_info.index_count;
}
for (std::uint32_t mi = c.first_mesh; mi < end && mi < data.meshes.size(); ++mi) {
const MeshInfo& m = data.meshes[mi];
if (!m.lod1_index_count) continue;
if (icur + m.lod1_index_count <= iraw.size() / 4)
std::memcpy(&data.indices[m.lod1_ebo_byte_offset / sizeof(std::uint32_t)], iu + icur, m.lod1_index_count * 4);
icur += m.lod1_index_count;
for (std::uint32_t mesh_index = sidecar_chunk.first_mesh;
mesh_index < end && mesh_index < data.meshes.size();
++mesh_index) {
const MeshInfo& mesh_info = data.meshes[mesh_index];
if (!mesh_info.lod1_index_count) continue;
if (icur + mesh_info.lod1_index_count <= iraw.size() / 4) {
std::memcpy(&data.indices[mesh_info.lod1_ebo_byte_offset / sizeof(std::uint32_t)],
iu + icur,
mesh_info.lod1_index_count * 4);
}
icur += mesh_info.lod1_index_count;
}
}
return data;
+54 -46
View File
@@ -26,37 +26,42 @@
#include <vector>
void reorderSidecarByMorton(SidecarData& sd) {
const std::size_t n = sd.meshes.size();
if (n < 2) return;
const std::size_t mesh_count = sd.meshes.size();
if (mesh_count < 2) return;
// Per-mesh centroid + instance count, exactly as the loader computes them
// before chunk planning (average of instance world-AABB centres).
std::vector<float> cx(n, 0.0f), cy(n, 0.0f), cz(n, 0.0f);
std::vector<std::uint32_t> cnt(n, 0);
std::vector<float> mesh_centroid_x(mesh_count, 0.0f),
mesh_centroid_y(mesh_count, 0.0f),
mesh_centroid_z(mesh_count, 0.0f);
std::vector<std::uint32_t> mesh_instance_count(mesh_count, 0);
for (const auto& inst : sd.instances) {
if (inst.mesh_id >= n) continue;
cx[inst.mesh_id] += 0.5f * (inst.world_aabb_min[0] + inst.world_aabb_max[0]);
cy[inst.mesh_id] += 0.5f * (inst.world_aabb_min[1] + inst.world_aabb_max[1]);
cz[inst.mesh_id] += 0.5f * (inst.world_aabb_min[2] + inst.world_aabb_max[2]);
++cnt[inst.mesh_id];
if (inst.mesh_id >= mesh_count) continue;
mesh_centroid_x[inst.mesh_id] += 0.5f * (inst.world_aabb_min[0] + inst.world_aabb_max[0]);
mesh_centroid_y[inst.mesh_id] += 0.5f * (inst.world_aabb_min[1] + inst.world_aabb_max[1]);
mesh_centroid_z[inst.mesh_id] += 0.5f * (inst.world_aabb_min[2] + inst.world_aabb_max[2]);
++mesh_instance_count[inst.mesh_id];
}
for (std::size_t i = 0; i < n; ++i) {
if (cnt[i] > 0) {
const float inv = 1.0f / float(cnt[i]);
cx[i] *= inv; cy[i] *= inv; cz[i] *= inv;
for (std::size_t i = 0; i < mesh_count; ++i) {
if (mesh_instance_count[i] > 0) {
const float inv = 1.0f / float(mesh_instance_count[i]);
mesh_centroid_x[i] *= inv;
mesh_centroid_y[i] *= inv;
mesh_centroid_z[i] *= inv;
}
}
// order[new_id] = old mesh id, in the loader's Morton order.
const std::vector<std::uint32_t> order =
ChunkPlanner::sortMeshIdsByMorton(n, cx, cy, cz, cnt);
ChunkPlanner::sortMeshIdsByMorton(
mesh_count, mesh_centroid_x, mesh_centroid_y, mesh_centroid_z, mesh_instance_count);
// Greedy-pack the sorted order into chunks (the same plan the loader used
// to derive). Each chunk is a CONSECUTIVE run of `order`, so once we lay
// meshes out in `order` the chunk is a contiguous mesh range — recorded in
// the TOC as {first_mesh, mesh_count}.
std::vector<std::uint32_t> mesh_vertex_count(n, 0);
for (std::size_t i = 0; i < n; ++i) mesh_vertex_count[i] = sd.meshes[i].vertex_count;
std::vector<std::uint32_t> mesh_vertex_count(mesh_count, 0);
for (std::size_t i = 0; i < mesh_count; ++i) mesh_vertex_count[i] = sd.meshes[i].vertex_count;
const std::vector<std::vector<std::uint32_t>> packed = ChunkPlanner::greedyPackChunks(
order, mesh_vertex_count, INSTANCED_VERTEX_STRIDE_BYTES,
WGPU_CHUNK_VERTEX_BYTES_LIMIT);
@@ -74,58 +79,61 @@ void reorderSidecarByMorton(SidecarData& sd) {
// MeshInfo.first_instance: the baker leaves it 0 for every mesh and stores
// instances ungrouped, so first_instance describes nothing. Grouping here
// by mesh_id both reorders instances correctly AND fixes first_instance.
std::vector<std::vector<std::uint32_t>> insts_by_mesh(n);
for (std::uint32_t ii = 0; ii < sd.instances.size(); ++ii) {
const std::uint32_t mid = sd.instances[ii].mesh_id;
if (mid < n) insts_by_mesh[mid].push_back(ii);
std::vector<std::vector<std::uint32_t>> insts_by_mesh(mesh_count);
for (std::uint32_t instance_index = 0; instance_index < sd.instances.size(); ++instance_index) {
const std::uint32_t mesh_id = sd.instances[instance_index].mesh_id;
if (mesh_id < mesh_count) insts_by_mesh[mesh_id].push_back(instance_index);
}
std::vector<std::uint8_t> new_vertices; new_vertices.reserve(sd.vertices.size());
std::vector<std::uint32_t> new_indices; new_indices.reserve(sd.indices.size());
std::vector<MeshInfo> new_meshes(n);
std::vector<MeshInfo> new_meshes(mesh_count);
std::vector<InstanceCpu> new_instances; new_instances.reserve(sd.instances.size());
// Pass A: vertices + LOD0 indices + instances, mesh-by-mesh in the new
// order, recording the new offsets on each MeshInfo.
for (std::uint32_t ni = 0; ni < n; ++ni) {
const std::uint32_t old = order[ni];
const MeshInfo& om = sd.meshes[old];
MeshInfo nm = om; // carries AABB; offsets/instance fields overwritten below
for (std::uint32_t new_mesh_index = 0; new_mesh_index < mesh_count; ++new_mesh_index) {
const std::uint32_t old = order[new_mesh_index];
const MeshInfo& old_mesh_info = sd.meshes[old];
MeshInfo new_mesh_info = old_mesh_info; // carries AABB; offsets/instance fields overwritten below
nm.vbo_byte_offset = std::uint32_t(new_vertices.size());
const std::size_t vbytes = std::size_t(om.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES;
new_mesh_info.vbo_byte_offset = std::uint32_t(new_vertices.size());
const std::size_t vbytes = std::size_t(old_mesh_info.vertex_count) * INSTANCED_VERTEX_STRIDE_BYTES;
new_vertices.insert(new_vertices.end(),
sd.vertices.begin() + om.vbo_byte_offset,
sd.vertices.begin() + om.vbo_byte_offset + vbytes);
sd.vertices.begin() + old_mesh_info.vbo_byte_offset,
sd.vertices.begin() + old_mesh_info.vbo_byte_offset + vbytes);
nm.ebo_byte_offset = std::uint32_t(new_indices.size() * sizeof(std::uint32_t));
const std::size_t i0 = om.ebo_byte_offset / sizeof(std::uint32_t);
new_mesh_info.ebo_byte_offset = std::uint32_t(new_indices.size() * sizeof(std::uint32_t));
const std::size_t i0 = old_mesh_info.ebo_byte_offset / sizeof(std::uint32_t);
new_indices.insert(new_indices.end(),
sd.indices.begin() + i0,
sd.indices.begin() + i0 + om.index_count);
sd.indices.begin() + i0 + old_mesh_info.index_count);
nm.first_instance = std::uint32_t(new_instances.size());
nm.instance_count = std::uint32_t(insts_by_mesh[old].size());
for (std::uint32_t ii : insts_by_mesh[old]) {
InstanceCpu ic = sd.instances[ii];
ic.mesh_id = ni;
new_instances.push_back(ic);
new_mesh_info.first_instance = std::uint32_t(new_instances.size());
new_mesh_info.instance_count = std::uint32_t(insts_by_mesh[old].size());
for (std::uint32_t instance_index : insts_by_mesh[old]) {
InstanceCpu instance = sd.instances[instance_index];
instance.mesh_id = new_mesh_index;
new_instances.push_back(instance);
}
new_meshes[ni] = nm;
new_meshes[new_mesh_index] = new_mesh_info;
}
// Pass B: LOD1 indices appended after all LOD0 (same global layout as the
// baker), in the new order, so a chunk's LOD1 slice is contiguous too.
for (std::uint32_t ni = 0; ni < n; ++ni) {
const MeshInfo& om = sd.meshes[order[ni]];
MeshInfo& nm = new_meshes[ni];
if (om.lod1_index_count == 0) { nm.lod1_ebo_byte_offset = 0; continue; }
nm.lod1_ebo_byte_offset = std::uint32_t(new_indices.size() * sizeof(std::uint32_t));
const std::size_t l0 = om.lod1_ebo_byte_offset / sizeof(std::uint32_t);
for (std::uint32_t new_mesh_index = 0; new_mesh_index < mesh_count; ++new_mesh_index) {
const MeshInfo& old_mesh_info = sd.meshes[order[new_mesh_index]];
MeshInfo& new_mesh_info = new_meshes[new_mesh_index];
if (old_mesh_info.lod1_index_count == 0) {
new_mesh_info.lod1_ebo_byte_offset = 0;
continue;
}
new_mesh_info.lod1_ebo_byte_offset = std::uint32_t(new_indices.size() * sizeof(std::uint32_t));
const std::size_t l0 = old_mesh_info.lod1_ebo_byte_offset / sizeof(std::uint32_t);
new_indices.insert(new_indices.end(),
sd.indices.begin() + l0,
sd.indices.begin() + l0 + om.lod1_index_count);
sd.indices.begin() + l0 + old_mesh_info.lod1_index_count);
}
sd.vertices = std::move(new_vertices);
+11 -11
View File
@@ -52,25 +52,25 @@ struct SidecarHeaderRaw {
// walks the metadata tail through one of these so a truncated buffer fails
// cleanly (return false) instead of reading out of bounds.
struct BufCursor {
const uint8_t* p;
size_t remaining;
const uint8_t* cursor;
size_t remaining_bytes;
bool take(void* dst, size_t bytes) {
if (bytes > remaining) return false;
std::memcpy(dst, p, bytes);
p += bytes;
remaining -= bytes;
if (bytes > remaining_bytes) return false;
std::memcpy(dst, cursor, bytes);
cursor += bytes;
remaining_bytes -= bytes;
return true;
}
// Read a uint32 length prefix followed by length*sizeof(T) elements.
template<typename T>
bool takeVec(std::vector<T>& v) {
bool takeVec(std::vector<T>& values) {
uint32_t n;
if (!take(&n, 4)) return false;
if (uint64_t(n) * sizeof(T) > remaining) return false;
v.resize(n);
if (n > 0 && !take(v.data(), size_t(n) * sizeof(T))) return false;
if (uint64_t(n) * sizeof(T) > remaining_bytes) return false;
values.resize(n);
if (n > 0 && !take(values.data(), size_t(n) * sizeof(T))) return false;
return true;
}
};
@@ -119,7 +119,7 @@ bool parseSidecarDeferred(const uint8_t* data, size_t n, SidecarData& out) {
if (!c.takeVec(out.elements)) return false;
uint32_t stbl_len = 0;
if (!c.take(&stbl_len, 4)) return false;
if (stbl_len > c.remaining) return false;
if (stbl_len > c.remaining_bytes) return false;
out.string_table.resize(stbl_len);
if (stbl_len > 0 && !c.take(out.string_table.data(), stbl_len)) return false;
return true;
+20 -20
View File
@@ -26,23 +26,23 @@ StreamingThread::~StreamingThread() {
}
void StreamingThread::start() {
std::unique_lock lk(mu_);
std::unique_lock lock(mu_);
if (running_) return;
shutdown_ = false;
running_ = true;
lk.unlock();
lock.unlock();
worker_ = std::thread(&StreamingThread::workerLoop, this);
}
void StreamingThread::stop() {
{
std::unique_lock lk(mu_);
std::unique_lock lock(mu_);
if (!running_) return;
shutdown_ = true;
}
cv_.notify_all();
if (worker_.joinable()) worker_.join();
std::unique_lock lk(mu_);
std::unique_lock lock(mu_);
running_ = false;
requests_.clear();
results_.clear();
@@ -50,7 +50,7 @@ void StreamingThread::stop() {
bool StreamingThread::enqueue(Request req) {
{
std::unique_lock lk(mu_);
std::unique_lock lock(mu_);
if (!running_ || shutdown_) return false;
requests_.push_back(std::move(req));
}
@@ -59,20 +59,20 @@ bool StreamingThread::enqueue(Request req) {
}
std::vector<StreamingThread::Result> StreamingThread::drainResults() {
std::vector<Result> out;
std::vector<Result> results;
{
std::unique_lock lk(mu_);
out.reserve(results_.size());
std::unique_lock lock(mu_);
results.reserve(results_.size());
while (!results_.empty()) {
out.push_back(std::move(results_.front()));
results.push_back(std::move(results_.front()));
results_.pop_front();
}
}
return out;
return results;
}
std::size_t StreamingThread::inFlightApprox() const {
std::unique_lock lk(mu_);
std::unique_lock lock(mu_);
return requests_.size() + (in_progress_ ? 1u : 0u);
}
@@ -80,8 +80,8 @@ void StreamingThread::workerLoop() {
for (;;) {
Request req;
{
std::unique_lock lk(mu_);
cv_.wait(lk, [this]() { return shutdown_ || !requests_.empty(); });
std::unique_lock lock(mu_);
cv_.wait(lock, [this]() { return shutdown_ || !requests_.empty(); });
if (shutdown_ && requests_.empty()) return;
req = std::move(requests_.front());
requests_.pop_front();
@@ -94,18 +94,18 @@ void StreamingThread::workerLoop() {
// us. The vbytes / idx buffers are allocated here on the worker
// thread — they cross back to the main thread when the result
// is drained and applied (pool.alloc + queueWriteBuffer).
Result res;
res.model_id = req.model_id;
res.chunk_idx = req.chunk_idx;
res.success = readChunkGeometryCompressed(
Result result;
result.model_id = req.model_id;
result.chunk_idx = req.chunk_idx;
result.success = readChunkGeometryCompressed(
req.file_path, req.geometry_section_offset,
req.v_comp_off, req.v_comp_size, req.v_raw_size,
req.i_comp_off, req.i_comp_size, req.i_raw_size,
res.vbytes, res.idx);
result.vbytes, result.idx);
{
std::unique_lock lk(mu_);
results_.push_back(std::move(res));
std::unique_lock lock(mu_);
results_.push_back(std::move(result));
in_progress_ = false;
}
}