Skip to main content

geometry_dag/
dgc.rs

1//! `.dgc`(Deep Geometry Clusters)流式二进制格式:分块 + 父子索引 + 边界锁定。
2//!
3//! 完整字节级规格见 `docs/dgc-format-spec.md`;本模块是唯一权威读写实现。
4//! 设计目标:
5//! - 全小端、段 payload 8 字节对齐,可直接映射到 typed array 视图;
6//! - 每段独立 CRC32C 校验,文件尺寸自锁定(total_file_size);
7//! - 保留字段必须为 0(读取即校验),为后续版本演进留护栏;
8//! - 段 payload 可选 zlib(RFC 1950)压缩,压缩比记录在段头。
9
10use std::io::Write;
11
12use flate2::write::ZlibEncoder;
13use flate2::Compression;
14
15use crate::error::{DagError, DagResult};
16use crate::dag::{DagLevel, MeshletDag};
17use crate::types::{MESHLET_MAX_TRIANGLES_LIMIT, MESHLET_MAX_VERTICES_LIMIT};
18
19/// 魔数 `DGC1`。
20pub const MAGIC: [u8; 4] = *b"DGC1";
21/// 当前格式版本。
22pub const FORMAT_VERSION: u32 = 1;
23/// 文件头尺寸(字节)。
24pub const FILE_HEADER_SIZE: usize = 64;
25/// 段头尺寸(字节)。
26pub const SECTION_HEADER_SIZE: usize = 88;
27/// 段 payload 对齐(字节)。
28pub const PAYLOAD_ALIGNMENT: usize = 8;
29
30/// 段类型:level 几何块。
31pub const SECTION_KIND_LEVEL: u32 = 0;
32/// 段类型:parents 块(kind=1 时 `level` 字段为细层号 k,连接 k 与 k+1)。
33pub const SECTION_KIND_PARENTS: u32 = 1;
34
35/// flags bit0:段 payload 为 zlib 压缩。
36pub const FLAG_ZLIB: u32 = 1 << 0;
37
38/// 无父簇哨兵(细簇在粗层无覆盖;对应 golden fixture 的 -1)。
39pub const NO_PARENT: u32 = u32::MAX;
40
41/// 序列化选项。
42#[derive(Debug, Clone, Copy)]
43pub struct DgcWriteOptions {
44    /// 是否 zlib 压缩段 payload(默认开:体积意识,不做硬门)。
45    pub compress: bool,
46}
47
48impl Default for DgcWriteOptions {
49    fn default() -> Self {
50        Self { compress: true }
51    }
52}
53
54/// 序列化 DAG 为 `.dgc` 字节流。
55///
56/// # Errors
57/// 层级/簇数超出 `u32` 可表示范围时返回 [`DagError::Overflow`]。
58pub fn write_dgc(dag: &MeshletDag, options: &DgcWriteOptions) -> DagResult<Vec<u8>> {
59    let level_count = dag.levels.len();
60    if level_count == 0 {
61        return Err(DagError::dgc_format("cannot serialize an empty DAG"));
62    }
63    let parent_pair_count = dag.parents_by_level.len();
64    if parent_pair_count != level_count.saturating_sub(1) {
65        return Err(DagError::dgc_format(format!(
66            "parents_by_level has {parent_pair_count} entries for {level_count} levels; expected {}",
67            level_count.saturating_sub(1)
68        )));
69    }
70    let section_count = level_count + parent_pair_count;
71    let level0 = &dag.levels[0];
72    let source_vertex_count = level0.positions.len() / 3;
73    let source_triangle_count = level0.indices.len() / 3;
74    if source_vertex_count > u32::MAX as usize || source_triangle_count > u32::MAX as usize {
75        return Err(DagError::overflow("source counts exceed u32"));
76    }
77
78    // 预编码全部段 payload,以便回填绝对偏移。
79    let mut sections: Vec<SectionRecord> = Vec::with_capacity(section_count);
80    for level in &dag.levels {
81        if level.meshlet_count > u32::MAX as usize {
82            return Err(DagError::overflow("meshlet count exceeds u32"));
83        }
84        let mut payload = Vec::new();
85        let counts = [
86            level.positions.len(),
87            level.indices.len(),
88            level.descriptors.len(),
89            level.vertex_remap.len(),
90            level.local_triangle_indices.len(),
91            level.bounds.len(),
92            level.source_triangles.len(),
93            level.cluster_source_spans.len(),
94        ];
95        extend_le_f32(&mut payload, &level.positions);
96        extend_le_u32(&mut payload, &level.indices);
97        extend_le_u32(&mut payload, &level.descriptors);
98        extend_le_u32(&mut payload, &level.vertex_remap);
99        extend_le_u32(&mut payload, &level.local_triangle_indices);
100        extend_le_f32(&mut payload, &level.bounds);
101        extend_le_u32(&mut payload, &level.source_triangles);
102        extend_le_u32(&mut payload, &level.cluster_source_spans);
103        sections.push(SectionRecord {
104            kind: SECTION_KIND_LEVEL,
105            level: level.level,
106            error: level.error,
107            counts,
108            raw: payload,
109            payload_offset: 0,
110        });
111    }
112    for (k, parents) in dag.parents_by_level.iter().enumerate() {
113        let k = k as u32;
114        if k + 1 >= level_count as u32 {
115            return Err(DagError::dgc_format("parents level index out of range"));
116        }
117        let coarse_count = dag.levels[k as usize + 1].meshlet_count;
118        for &parent in parents {
119            if parent != NO_PARENT && parent as usize >= coarse_count {
120                return Err(DagError::dgc_format(format!(
121                    "parent {parent} out of range for coarse level {} ({coarse_count} clusters)",
122                    k + 1
123                )));
124            }
125        }
126        let mut payload = Vec::new();
127        extend_le_u32(&mut payload, parents);
128        sections.push(SectionRecord {
129            kind: SECTION_KIND_PARENTS,
130            level: k,
131            error: 0.0,
132            counts: [parents.len(), 0, 0, 0, 0, 0, 0, 0],
133            raw: payload,
134            payload_offset: 0,
135        });
136    }
137
138    // 文件头 + 段头区,然后逐段 8 对齐 payload。
139    let header_span = FILE_HEADER_SIZE + SECTION_HEADER_SIZE * section_count;
140    let mut offset = header_span;
141    let mut stored_payloads: Vec<Vec<u8>> = Vec::with_capacity(sections.len());
142    for section in &mut sections {
143        let stored = if options.compress {
144            let mut encoder = ZlibEncoder::new(Vec::new(), Compression::new(6));
145            encoder
146                .write_all(&section.raw)
147                .map_err(|e| DagError::Io { path: std::path::PathBuf::from("<memory>"), source: e })?;
148            encoder.finish().map_err(|e| DagError::Io {
149                path: std::path::PathBuf::from("<memory>"),
150                source: std::io::Error::other(e),
151            })?
152        } else {
153            section.raw.clone()
154        };
155        offset = align_up(offset, PAYLOAD_ALIGNMENT);
156        section.payload_offset = offset as u64;
157        offset += stored.len();
158        stored_payloads.push(stored);
159    }
160    let total_size = offset;
161    // 边界锁定护栏:单文件 64 GiB 上限(防错位字段导致的荒谬分配)。
162    if total_size as u64 > 64_u64 * 1024 * 1024 * 1024 {
163        return Err(DagError::overflow("serialized DAG exceeds 64 GiB sanity bound"));
164    }
165
166    let mut out = Vec::with_capacity(total_size);
167    // 文件头(64B)。
168    out.extend_from_slice(&MAGIC);
169    extend_le_u32(&mut out, &[FORMAT_VERSION]);
170    let mut flags = 0u32;
171    if options.compress {
172        flags |= FLAG_ZLIB;
173    }
174    extend_le_u32(&mut out, &[flags]);
175    extend_le_u32(&mut out, &[level_count as u32]);
176    extend_le_u32(&mut out, &[parent_pair_count as u32]);
177    extend_le_u32(&mut out, &[source_vertex_count as u32]);
178    extend_le_u32(&mut out, &[source_triangle_count as u32]);
179    extend_le_u32(&mut out, &[level0.max_vertices]);
180    extend_le_u32(&mut out, &[level0.max_triangles]);
181    extend_le_u64(&mut out, &[total_size as u64]);
182    // reserved 20 字节(读取校验必须全 0):文件头精确 64 字节。
183    out.extend_from_slice(&[0u8; 20]);
184    debug_assert_eq!(out.len(), FILE_HEADER_SIZE);
185
186    // 段头 ×N。
187    for (index, (section, stored)) in sections.iter().zip(&stored_payloads).enumerate() {
188        extend_le_u32(&mut out, &[section.kind]);
189        extend_le_u32(&mut out, &[section.level]);
190        extend_le_f64(&mut out, &[section.error]);
191        extend_le_u32(&mut out, &section.counts.map(|c| u32::try_from(c).unwrap_or(u32::MAX)));
192        extend_le_u64(&mut out, &[section.raw.len() as u64]);
193        extend_le_u64(&mut out, &[stored.len() as u64]);
194        extend_le_u64(&mut out, &[section.payload_offset]);
195        extend_le_u32(&mut out, &[crc32c(&section.raw)]);
196        // reserved 12 字节(读取校验必须全 0):段头精确 88 字节。
197        extend_le_u32(&mut out, &[0]);
198        extend_le_u32(&mut out, &[0]);
199        extend_le_u32(&mut out, &[0]);
200        debug_assert_eq!(out.len(), FILE_HEADER_SIZE + (index + 1) * SECTION_HEADER_SIZE);
201    }
202    debug_assert_eq!(out.len(), header_span);
203
204    // payload 区(8 对齐补零)。
205    for (section, stored) in sections.iter().zip(&stored_payloads) {
206        let aligned = align_up(out.len(), PAYLOAD_ALIGNMENT);
207        out.resize(aligned, 0);
208        debug_assert_eq!(out.len() as u64, section.payload_offset);
209        out.extend_from_slice(stored);
210    }
211    debug_assert_eq!(out.len(), total_size);
212    Ok(out)
213}
214
215/// 解析并完整校验 `.dgc` 字节流(magic/版本/尺寸锁定/保留字段/CRC/段间一致性)。
216///
217/// # Errors
218/// 任何结构校验失败返回 [`DagError::DgcFormat`],带失败点说明。
219pub fn read_dgc(bytes: &[u8]) -> DagResult<MeshletDag> {
220    let mut cursor = Reader::new(bytes);
221    let mut magic = [0u8; 4];
222    cursor.read_exact_into(&mut magic)?;
223    if magic != MAGIC {
224        return Err(DagError::dgc_format(format!("bad magic {magic:?}, expected {MAGIC:?}")));
225    }
226    let version = cursor.read_u32()?;
227    if version != FORMAT_VERSION {
228        return Err(DagError::dgc_format(format!("unsupported version {version}, expected {FORMAT_VERSION}")));
229    }
230    let flags = cursor.read_u32()?;
231    if flags & !FLAG_ZLIB != 0 {
232        return Err(DagError::dgc_format(format!("unknown flags {flags:#x}")));
233    }
234    let compressed = flags & FLAG_ZLIB != 0;
235    let level_count = cursor.read_u32()? as usize;
236    let parent_pair_count = cursor.read_u32()? as usize;
237    if level_count == 0 {
238        return Err(DagError::dgc_format("zero level count"));
239    }
240    if parent_pair_count != level_count - 1 {
241        return Err(DagError::dgc_format(format!(
242            "parent pairs {parent_pair_count} inconsistent with {level_count} levels"
243        )));
244    }
245    let source_vertex_count = cursor.read_u32()? as usize;
246    let source_triangle_count = cursor.read_u32()? as usize;
247    let max_vertices = cursor.read_u32()?;
248    let max_triangles = cursor.read_u32()?;
249    let total_size = cursor.read_u64()? as usize;
250    let reserved = cursor.read_bytes(20)?;
251    if reserved.iter().any(|&b| b != 0) {
252        return Err(DagError::dgc_format("header reserved bytes must be zero"));
253    }
254    if total_size != bytes.len() {
255        return Err(DagError::dgc_format(format!(
256            "size lock mismatch: header says {total_size}, file is {}",
257            bytes.len()
258        )));
259    }
260    if max_vertices > MESHLET_MAX_VERTICES_LIMIT || max_triangles > MESHLET_MAX_TRIANGLES_LIMIT {
261        return Err(DagError::dgc_format(
262            "cluster limits exceed schema caps (file from a newer build?)",
263        ));
264    }
265
266    let section_count = level_count + parent_pair_count;
267    let header_span = FILE_HEADER_SIZE + SECTION_HEADER_SIZE * section_count;
268    if bytes.len() < header_span {
269        return Err(DagError::dgc_format(format!(
270            "truncated: {} bytes < header span {header_span}",
271            bytes.len()
272        )));
273    }
274
275    let mut levels: Vec<DagLevel> = Vec::with_capacity(level_count);
276    let mut parents_by_level: Vec<Vec<u32>> = Vec::with_capacity(parent_pair_count);
277    for index in 0..section_count {
278        let header_base = FILE_HEADER_SIZE + SECTION_HEADER_SIZE * index;
279        let mut header = Reader::new(&bytes[header_base..header_base + SECTION_HEADER_SIZE]);
280        let kind = header.read_u32()?;
281        let level_index = header.read_u32()?;
282        let error = header.read_f64()?;
283        let mut counts = [0u32; 8];
284        for count in &mut counts {
285            *count = header.read_u32()?;
286        }
287        let raw_size = header.read_u64()? as usize;
288        let stored_size = header.read_u64()? as usize;
289        let payload_offset = header.read_u64()? as usize;
290        let crc = header.read_u32()?;
291        let reserved_a = header.read_u32()?;
292        let reserved_b = header.read_u32()?;
293        let reserved_c = header.read_u32()?;
294        if reserved_a != 0 || reserved_b != 0 || reserved_c != 0 {
295            return Err(DagError::dgc_format("section reserved bytes must be zero"));
296        }
297        if !payload_offset.is_multiple_of(PAYLOAD_ALIGNMENT) {
298            return Err(DagError::dgc_format("payload offset must be 8-byte aligned"));
299        }
300        if payload_offset.checked_add(stored_size).is_none_or(|end| end > bytes.len()) {
301            return Err(DagError::dgc_format(format!(
302                "section {index} payload [{payload_offset}, {} ) exceeds file size {}",
303                payload_offset + stored_size,
304                bytes.len()
305            )));
306        }
307        let stored = &bytes[payload_offset..payload_offset + stored_size];
308        let raw: Vec<u8> = if compressed {
309            decode_zlib(stored, raw_size)?
310        } else {
311            if stored_size != raw_size {
312                return Err(DagError::dgc_format(format!(
313                    "section {index}: uncompressed stored size {stored_size} != raw size {raw_size}"
314                )));
315            }
316            stored.to_vec()
317        };
318        if raw.len() != raw_size {
319            return Err(DagError::dgc_format(format!(
320                "section {index}: raw size {} != header {raw_size}",
321                raw.len()
322            )));
323        }
324        let actual_crc = crc32c(&raw);
325        if actual_crc != crc {
326            return Err(DagError::dgc_format(format!(
327                "section {index} crc mismatch: file {crc:#010x}, computed {actual_crc:#010x}"
328            )));
329        }
330
331        match kind {
332            SECTION_KIND_LEVEL => {
333                let mut payload = Reader::new(&raw);
334                let [position_count, index_count, descriptor_count, remap_count, local_tri_count, bounds_count, source_tri_count, span_count] =
335                    counts;
336                let expected_raw = 4 * (position_count + index_count + descriptor_count + remap_count
337                    + local_tri_count + bounds_count + source_tri_count + span_count)
338                    as u64;
339                if expected_raw != raw_size as u64 {
340                    return Err(DagError::dgc_format(format!(
341                        "level section {level_index}: counts imply {expected_raw} bytes, header says {raw_size}"
342                    )));
343                }
344                let positions = payload.read_f32_vec(position_count as usize)?;
345                let indices = payload.read_u32_vec(index_count as usize)?;
346                let descriptors = payload.read_u32_vec(descriptor_count as usize)?;
347                let vertex_remap = payload.read_u32_vec(remap_count as usize)?;
348                let local_triangle_indices = payload.read_u32_vec(local_tri_count as usize)?;
349                let bounds = payload.read_f32_vec(bounds_count as usize)?;
350                let source_triangles = payload.read_u32_vec(source_tri_count as usize)?;
351                let cluster_source_spans = payload.read_u32_vec(span_count as usize)?;
352                if payload.remaining() != 0 {
353                    return Err(DagError::dgc_format(format!(
354                        "level section {level_index}: {} trailing bytes",
355                        payload.remaining()
356                    )));
357                }
358                // 结构不变量:段表长度 == 簇数,前缀和与 indices 一致。
359                if descriptor_count / 4 != span_count / 2 {
360                    return Err(DagError::dgc_format(format!(
361                        "level section {level_index}: {} span pairs inconsistent with {} meshlets",
362                        span_count / 2,
363                        descriptor_count / 4
364                    )));
365                }
366                if position_count % 3 != 0 || index_count % 3 != 0 {
367                    return Err(DagError::dgc_format(format!(
368                        "level section {level_index}: position/index counts must be triples"
369                    )));
370                }
371                let mut expected_start = 0u32;
372                for span in cluster_source_spans.chunks_exact(2) {
373                    if span[0] != expected_start || span[1] < span[0] {
374                        return Err(DagError::dgc_format(format!(
375                            "level section {level_index}: cluster source spans not a prefix-sum chain"
376                        )));
377                    }
378                    expected_start = span[1];
379                }
380                if expected_start != index_count / 3 {
381                    return Err(DagError::dgc_format(format!(
382                        "level section {level_index}: spans cover {} triangles, indices have {}",
383                        expected_start,
384                        index_count / 3
385                    )));
386                }
387                for &index in &indices {
388                    if index >= position_count / 3 {
389                        return Err(DagError::dgc_format(format!(
390                            "level section {level_index}: index {index} out of vertex range"
391                        )));
392                    }
393                }
394                levels.push(DagLevel {
395                    level: level_index,
396                    error,
397                    positions,
398                    indices,
399                    meshlet_count: descriptor_count as usize / 4,
400                    max_vertices,
401                    max_triangles,
402                    descriptors,
403                    vertex_remap,
404                    local_triangle_indices,
405                    bounds,
406                    source_triangles,
407                    cluster_source_spans,
408                });
409            }
410            SECTION_KIND_PARENTS => {
411                // 段顺序合同:parents 段必须出现在全部 level 段之后。
412                if levels.len() != level_count {
413                    return Err(DagError::dgc_format(
414                        "parents section encountered before all level sections (order contract violated)",
415                    ));
416                }
417                let fine = level_index as usize;
418                if fine >= level_count - 1 {
419                    return Err(DagError::dgc_format(format!(
420                        "parents section references level {fine} as fine side, but last level is {}",
421                        level_count - 1
422                    )));
423                }
424                let mut payload = Reader::new(&raw);
425                let parents = payload.read_u32_vec(counts[0] as usize)?;
426                if payload.remaining() != 0 {
427                    return Err(DagError::dgc_format("parents section has trailing bytes"));
428                }
429                let fine_clusters = levels[fine].meshlet_count;
430                if parents.len() != fine_clusters {
431                    return Err(DagError::dgc_format(format!(
432                        "parents section for level {fine}: {} entries, fine level has {fine_clusters} clusters",
433                        parents.len()
434                    )));
435                }
436                let coarse_count = levels[fine + 1].meshlet_count;
437                for (cluster, &parent) in parents.iter().enumerate() {
438                    if parent != NO_PARENT && parent as usize >= coarse_count {
439                        return Err(DagError::dgc_format(format!(
440                            "parents section for level {fine}: cluster {cluster} parent {parent} out of range"
441                        )));
442                    }
443                }
444                parents_by_level.push(parents);
445            }
446            other => {
447                return Err(DagError::dgc_format(format!("unknown section kind {other}")));
448            }
449        }
450    }
451    if levels.len() != level_count || parents_by_level.len() != parent_pair_count {
452        return Err(DagError::dgc_format("section count mismatch after parse"));
453    }
454    // 顶层一致性:level 0 尺寸与文件头一致。
455    if levels[0].positions.len() / 3 != source_vertex_count
456        || levels[0].indices.len() / 3 != source_triangle_count
457    {
458        return Err(DagError::dgc_format(
459            "level 0 geometry size disagrees with file header",
460        ));
461    }
462    Ok(MeshletDag { levels, parents_by_level })
463}
464
465// —— 内部工具 ——
466
467struct SectionRecord {
468    kind: u32,
469    level: u32,
470    error: f64,
471    counts: [usize; 8],
472    raw: Vec<u8>,
473    payload_offset: u64,
474}
475
476fn align_up(value: usize, alignment: usize) -> usize {
477    value.div_ceil(alignment) * alignment
478}
479
480fn extend_le_u32(out: &mut Vec<u8>, values: &[u32]) {
481    for &value in values {
482        out.extend_from_slice(&value.to_le_bytes());
483    }
484}
485
486fn extend_le_u64(out: &mut Vec<u8>, values: &[u64]) {
487    for &value in values {
488        out.extend_from_slice(&value.to_le_bytes());
489    }
490}
491
492fn extend_le_f32(out: &mut Vec<u8>, values: &[f32]) {
493    for &value in values {
494        out.extend_from_slice(&value.to_le_bytes());
495    }
496}
497
498fn extend_le_f64(out: &mut Vec<u8>, values: &[f64]) {
499    for &value in values {
500        out.extend_from_slice(&value.to_le_bytes());
501    }
502}
503
504/// 顺序字节读取器(边界检查集中在一处)。
505struct Reader<'a> {
506    bytes: &'a [u8],
507    pos: usize,
508}
509
510impl<'a> Reader<'a> {
511    fn new(bytes: &'a [u8]) -> Self {
512        Self { bytes, pos: 0 }
513    }
514
515    fn read_bytes(&mut self, len: usize) -> DagResult<&'a [u8]> {
516        let end = self.pos.checked_add(len).ok_or_else(|| DagError::dgc_format("size overflow"))?;
517        if end > self.bytes.len() {
518            return Err(DagError::dgc_format(format!(
519                "truncated: need {len} bytes at offset {}, only {} remain",
520                self.pos,
521                self.bytes.len() - self.pos
522            )));
523        }
524        let slice = &self.bytes[self.pos..end];
525        self.pos = end;
526        Ok(slice)
527    }
528
529    fn read_exact_into(&mut self, out: &mut [u8]) -> DagResult<()> {
530        let slice = self.read_bytes(out.len())?;
531        out.copy_from_slice(slice);
532        Ok(())
533    }
534
535    fn read_u32(&mut self) -> DagResult<u32> {
536        let slice = self.read_bytes(4)?;
537        Ok(u32::from_le_bytes(slice.try_into().expect("4 bytes")))
538    }
539
540    fn read_u64(&mut self) -> DagResult<u64> {
541        let slice = self.read_bytes(8)?;
542        Ok(u64::from_le_bytes(slice.try_into().expect("8 bytes")))
543    }
544
545    fn read_f64(&mut self) -> DagResult<f64> {
546        Ok(f64::from_bits(self.read_u64()?))
547    }
548
549    fn read_u32_vec(&mut self, count: usize) -> DagResult<Vec<u32>> {
550        let slice = self.read_bytes(count * 4)?;
551        Ok(slice
552            .chunks_exact(4)
553            .map(|c| u32::from_le_bytes(c.try_into().expect("4 bytes")))
554            .collect())
555    }
556
557    fn read_f32_vec(&mut self, count: usize) -> DagResult<Vec<f32>> {
558        let slice = self.read_bytes(count * 4)?;
559        Ok(slice
560            .chunks_exact(4)
561            .map(|c| f32::from_le_bytes(c.try_into().expect("4 bytes")))
562            .collect())
563    }
564
565    fn remaining(&self) -> usize {
566        self.bytes.len() - self.pos
567    }
568}
569
570/// zlib 解压并锁定目标尺寸(解压炸弹防护:输出超过 `raw_size` 即拒绝)。
571fn decode_zlib(data: &[u8], raw_size: usize) -> DagResult<Vec<u8>> {
572    use std::io::Read;
573    let decoder = flate2::read::ZlibDecoder::new(data);
574    let mut out = Vec::with_capacity(raw_size.min(1 << 28));
575    decoder
576        .take(raw_size as u64 + 1)
577        .read_to_end(&mut out)
578        .map_err(|e| DagError::dgc_format(format!("zlib decode failed: {e}")))?;
579    if out.len() != raw_size {
580        return Err(DagError::dgc_format(format!(
581            "zlib payload size {} != header raw size {raw_size}",
582            out.len()
583        )));
584    }
585    Ok(out)
586}
587
588/// CRC32C(Castagnoli,iSCSI 反射多项式),查表实现。
589#[must_use]
590pub fn crc32c(data: &[u8]) -> u32 {
591    const POLY: u32 = 0x82f6_3b78; // 反射 0x1EDC6F41
592    static TABLE: std::sync::OnceLock<[u32; 256]> = std::sync::OnceLock::new();
593    let table = TABLE.get_or_init(|| {
594        let mut table = [0u32; 256];
595        for (i, entry) in table.iter_mut().enumerate() {
596            let mut crc = i as u32;
597            for _ in 0..8 {
598                crc = if crc & 1 != 0 { (crc >> 1) ^ POLY } else { crc >> 1 };
599            }
600            *entry = crc;
601        }
602        table
603    });
604    let mut crc = 0xFFFF_FFFFu32;
605    for &byte in data {
606        crc = (crc >> 8) ^ table[((crc ^ u32::from(byte)) & 0xFF) as usize];
607    }
608    crc ^ 0xFFFF_FFFF
609}
610
611impl DagLevel {
612    /// 该层簇顶点上限(构建参数,随层持久化)。
613    #[must_use]
614    pub fn max_vertices(&self) -> u32 {
615        self.max_vertices
616    }
617
618    /// 该层簇三角形上限(构建参数,随层持久化)。
619    #[must_use]
620    pub fn max_triangles(&self) -> u32 {
621        self.max_triangles
622    }
623}
624
625#[cfg(test)]
626mod tests {
627    use super::*;
628    use crate::dag::{build_meshlet_dag, DagOptions};
629    use crate::types::IndexedGeometry;
630
631    fn sphere_geometry(segments: usize, rings: usize) -> IndexedGeometry {
632        let (positions, indices) = crate::simplify::test_support::test_sphere(segments, rings);
633        IndexedGeometry { positions, indices }
634    }
635
636    fn sample_dag() -> MeshletDag {
637        build_meshlet_dag(&sphere_geometry(24, 12), &DagOptions { levels: Some(4), ..Default::default() })
638            .expect("dag")
639    }
640
641    #[test]
642    fn roundtrip_all_levels_bit_exact() {
643        let dag = sample_dag();
644        for compress in [true, false] {
645            let bytes = write_dgc(&dag, &DgcWriteOptions { compress }).expect("write");
646            let back = read_dgc(&bytes).expect("read");
647            assert_eq!(back.levels.len(), dag.levels.len());
648            for (a, b) in dag.levels.iter().zip(&back.levels) {
649                assert_eq!(a.level, b.level);
650                assert_eq!(a.error.to_bits(), b.error.to_bits());
651                assert_eq!(a.positions, b.positions);
652                assert_eq!(a.indices, b.indices);
653                assert_eq!(a.descriptors, b.descriptors);
654                assert_eq!(a.vertex_remap, b.vertex_remap);
655                assert_eq!(a.local_triangle_indices, b.local_triangle_indices);
656                assert_eq!(a.bounds, b.bounds);
657                assert_eq!(a.source_triangles, b.source_triangles);
658                assert_eq!(a.cluster_source_spans, b.cluster_source_spans);
659            }
660            assert_eq!(back.parents_by_level, dag.parents_by_level);
661        }
662    }
663
664    #[test]
665    fn truncated_file_is_rejected() {
666        let bytes = write_dgc(&sample_dag(), &DgcWriteOptions::default()).expect("write");
667        assert!(read_dgc(&bytes[..bytes.len() - 1]).is_err());
668        assert!(read_dgc(&bytes[..40]).is_err());
669    }
670
671    #[test]
672    fn corrupted_byte_detected_by_crc() {
673        let mut bytes = write_dgc(&sample_dag(), &DgcWriteOptions::default()).expect("write");
674        let last = bytes.len() - 1;
675        bytes[last] ^= 0xFF;
676        assert!(read_dgc(&bytes).is_err());
677    }
678
679    #[test]
680    fn bad_magic_rejected() {
681        let mut bytes = write_dgc(&sample_dag(), &DgcWriteOptions::default()).expect("write");
682        bytes[0] = b'X';
683        let err = read_dgc(&bytes).unwrap_err();
684        assert!(err.to_string().contains("bad magic"));
685    }
686
687    #[test]
688    fn nonempty_reserved_must_be_zero() {
689        let mut bytes = write_dgc(&sample_dag(), &DgcWriteOptions::default()).expect("write");
690        bytes[0x30] = 1; // header reserved 区
691        let err = read_dgc(&bytes).unwrap_err();
692        assert!(err.to_string().contains("reserved"));
693    }
694
695    #[test]
696    fn empty_dag_rejected() {
697        let empty = MeshletDag { levels: vec![], parents_by_level: vec![] };
698        assert!(write_dgc(&empty, &DgcWriteOptions::default()).is_err());
699    }
700
701    #[test]
702    fn compression_shrinks_or_ties() {
703        let dag = sample_dag();
704        let raw = write_dgc(&dag, &DgcWriteOptions { compress: false }).expect("raw");
705        let zipped = write_dgc(&dag, &DgcWriteOptions { compress: true }).expect("zip");
706        assert!(zipped.len() <= raw.len(), "zlib should not inflate this payload");
707    }
708
709    #[test]
710    fn crc32c_known_vector() {
711        // iSCSI 标准测试向量:"123456789" -> 0xE3069283
712        assert_eq!(crc32c(b"123456789"), 0xE306_9283);
713    }
714}