Skip to main content

draco_core/
point_cloud_decoder.rs

1use crate::compression_config::EncodedGeometryType;
2#[cfg(feature = "point_cloud_decode")]
3use crate::decoder_buffer::DecoderBuffer;
4#[cfg(feature = "point_cloud_decode")]
5use crate::draco_types::DataType;
6#[cfg(feature = "point_cloud_decode")]
7use crate::geometry_attribute::{GeometryAttributeType, PointAttribute};
8#[cfg(feature = "point_cloud_decode")]
9use crate::kd_tree_attributes_decoder::KdTreeAttributesDecoder;
10#[cfg(feature = "point_cloud_decode")]
11use crate::parallel;
12#[cfg(feature = "point_cloud_decode")]
13use crate::point_cloud::PointCloud;
14#[cfg(feature = "point_cloud_decode")]
15use crate::prediction_scheme::EntryToPointIdMap;
16#[cfg(feature = "point_cloud_decode")]
17use crate::sequential_integer_attribute_decoder::{
18    PortableExtent, SequentialIntegerAttributeDecoder,
19};
20#[cfg(feature = "point_cloud_decode")]
21use crate::status::{DracoError, Status};
22#[cfg(feature = "point_cloud_decode")]
23use crate::symbol_encoding::{decode_raw_symbol_pair, decode_raw_symbol_pair_in};
24
25#[cfg(feature = "point_cloud_decode")]
26use crate::attribute_octahedron_transform::AttributeOctahedronTransform;
27#[cfg(feature = "point_cloud_decode")]
28use crate::attribute_quantization_transform::AttributeQuantizationTransform;
29#[cfg(feature = "point_cloud_decode")]
30use crate::attribute_transform::AttributeTransform;
31#[cfg(feature = "point_cloud_decode")]
32use crate::sequential_generic_attribute_decoder::SequentialGenericAttributeDecoder;
33#[cfg(feature = "point_cloud_decode")]
34use crate::sequential_normal_attribute_decoder::SequentialNormalAttributeDecoder;
35#[cfg(feature = "point_cloud_decode")]
36use crate::sequential_quantization_attribute_decoder::SequentialQuantizationAttributeDecoder;
37#[cfg(feature = "point_cloud_decode")]
38use crate::version::{version_at_least, VERSION_FLAGS_INTRODUCED};
39
40/// Whether a prediction transform byte follows this prediction method byte.
41///
42/// Upstream writes the transform only when the method is not `PREDICTION_NONE`,
43/// which is `-2` and reaches the stream as `0xFE`. `0xFF` is `-1`, which this
44/// crate once wrote for the same meaning, so both are read as "nothing follows"
45/// -- the same pair `SequentialIntegerAttributeDecoder` accepts.
46///
47/// The pre-1.2 shims below need this because they walk the prediction header by
48/// hand to reach the quantization parameters behind it. Testing `0xFF` alone
49/// made them step one byte into a `PREDICTION_NONE` stream and read the
50/// parameters shifted: the range came out zero and every position dequantized to
51/// the origin, with the point and face counts still right and the decode still
52/// reporting success. Draco writes `PREDICTION_NONE` at compression level 0.
53/// Gated on the feature its callers live behind, and only that one: both of
54/// them are now the pre-2.0 shims inside the shared normal and quantization
55/// decoders, so a `point_cloud_decode` build without legacy support compiles
56/// neither and would carry this as dead code.
57#[cfg(feature = "legacy_bitstream_decode")]
58pub(crate) fn carries_transform_byte(method_byte: u8) -> bool {
59    method_byte != 0xFF && method_byte != 0xFE
60}
61
62/// Decoder for Draco point cloud bitstreams.
63///
64/// `PointCloudDecoder` reads a point-cloud `.drc` bitstream and reconstructs a
65/// [`PointCloud`] with its attributes and metadata. Both
66/// KD-tree and sequential attribute encodings are supported (the actual decode
67/// requires the `point_cloud_decode` feature).
68///
69/// A round trip is shown on the `PointCloudEncoder` type docs.
70pub struct PointCloudDecoder {
71    geometry_type: EncodedGeometryType,
72    #[cfg(feature = "point_cloud_decode")]
73    method: u8,
74    #[cfg(feature = "point_cloud_decode")]
75    flags: u16,
76    /// Ungated, unlike the fields above: `bitstream_version` is read by the
77    /// attribute decoders on both the mesh and the point-cloud path, and the
78    /// mesh path exists without `point_cloud_decode`.
79    version_major: u8,
80    version_minor: u8,
81    #[cfg(feature = "point_cloud_decode")]
82    threads: i32,
83}
84
85/// The stream length below which a point cloud stays on the calling thread. A
86/// stream this small claiming this many values is what the allocation budget
87/// exists for, and it is read by one thread and one budget.
88///
89/// A fuzzing build takes it down with the other thread gates; see
90/// `parallel::ATTRIBUTES_MIN_VALUES`.
91#[cfg(all(feature = "point_cloud_decode", not(fuzzing)))]
92const PARALLEL_MIN_STREAM_BYTES: usize = 1 << 20;
93#[cfg(all(feature = "point_cloud_decode", fuzzing))]
94const PARALLEL_MIN_STREAM_BYTES: usize = 64;
95
96/// The components an attribute's integer stream codes, for the sequential
97/// decoder types that have one (1 to 3): a normal's are the two of its
98/// octahedral coordinates, not the attribute's three.
99#[cfg(feature = "point_cloud_decode")]
100fn coded_components(decoder_type: u8, attribute: &PointAttribute) -> usize {
101    match decoder_type {
102        3 => 2,
103        _ => attribute.num_components() as usize,
104    }
105}
106
107// How many attribute streams this thread has decoded side by side, for the
108// tests that must see the parallel path run rather than fall back.
109#[cfg(all(test, feature = "point_cloud_decode"))]
110thread_local! {
111    static ENGAGED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
112    // How many jobs this thread ran on symbols decoded beside another's.
113    static PAIRED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
114    // Set by the tests that need the decode in order, attribute by attribute,
115    // as the reference the side-by-side decode is held to.
116    static IN_ORDER: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
117    // Set by the tests that hold the decode in order with its pairs to the one
118    // without them.
119    static PAIRS_OFF: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
120}
121
122/// A quantized attribute whose values are decoded and whose inverse transform
123/// has not yet run.
124#[cfg(feature = "point_cloud_decode")]
125struct PendingQuant {
126    att_id: i32,
127    portable: PointAttribute,
128    transform: AttributeQuantizationTransform,
129}
130
131/// A normal attribute whose octahedral values are decoded and whose inverse
132/// transform has not yet run.
133#[cfg(feature = "point_cloud_decode")]
134struct PendingNormal {
135    att_id: i32,
136    portable: PointAttribute,
137    quantization_bits: u8,
138}
139
140/// Runs the inverse quantization of every pending attribute, one thread each at
141/// most. Each writes only its own attribute, so the attributes are taken out of
142/// the cloud for the duration and put back. They are taken in attribute order,
143/// whatever order the decode left them pending in, so the first error in that
144/// order is the one reported, on any number of threads.
145#[cfg(feature = "point_cloud_decode")]
146fn dequantize_in_parallel(
147    pc: &mut PointCloud,
148    mut pending: Vec<PendingQuant>,
149    threads: usize,
150) -> Status {
151    pending.sort_by_key(|q| q.att_id);
152    let mut work: Vec<(PendingQuant, PointAttribute, Option<DracoError>)> =
153        Vec::with_capacity(pending.len());
154    for q in pending {
155        let dst = std::mem::take(pc.try_attribute_mut(q.att_id)?);
156        work.push((q, dst, None));
157    }
158    parallel::for_each_chunk_mut(&mut work, 1, threads, |_, chunk| {
159        let (q, dst, error) = &mut chunk[0];
160        if let Err(e) = q.transform.inverse_transform_attribute(&q.portable, dst) {
161            *error = Some(DracoError::general(format!(
162                "Failed to dequantize attribute: {e}"
163            )));
164        }
165    });
166    let mut first_error = None;
167    for (q, dst, error) in work {
168        *pc.try_attribute_mut(q.att_id)? = dst;
169        first_error = first_error.or(error);
170    }
171    first_error.map_or(Ok(()), Err)
172}
173
174/// [`dequantize_in_parallel`] for the octahedral normals.
175#[cfg(feature = "point_cloud_decode")]
176fn undo_octahedra_in_parallel(
177    pc: &mut PointCloud,
178    mut pending: Vec<PendingNormal>,
179    threads: usize,
180) -> Status {
181    pending.sort_by_key(|n| n.att_id);
182    let mut work: Vec<(PendingNormal, PointAttribute, Option<DracoError>)> =
183        Vec::with_capacity(pending.len());
184    for n in pending {
185        let dst = std::mem::take(pc.try_attribute_mut(n.att_id)?);
186        work.push((n, dst, None));
187    }
188    parallel::for_each_chunk_mut(&mut work, 1, threads, |_, chunk| {
189        let (n, dst, error) = &mut chunk[0];
190        let mut oct = AttributeOctahedronTransform::new(-1);
191        let done = oct
192            .set_parameters(n.quantization_bits as i32)
193            .and_then(|()| oct.inverse_transform_attribute(&n.portable, dst));
194        if let Err(e) = done {
195            *error = Some(DracoError::general(format!(
196                "Failed to decode normals: {e}"
197            )));
198        }
199    });
200    let mut first_error = None;
201    for (n, dst, error) in work {
202        *pc.try_attribute_mut(n.att_id)? = dst;
203        first_error = first_error.or(error);
204    }
205    first_error.map_or(Ok(()), Err)
206}
207
208impl Default for PointCloudDecoder {
209    fn default() -> Self {
210        Self::new()
211    }
212}
213
214#[cfg(feature = "point_cloud_decode")]
215fn validate_num_attributes_in_decoder(
216    num_attributes_in_decoder: usize,
217    remaining_bytes: usize,
218) -> Result<(), DracoError> {
219    // Each attribute must have at least type, data type, component count,
220    // normalized flag, unique id, and a decoder type byte. Reject impossible
221    // counts before reserving vectors from untrusted input.
222    const MIN_ATTRIBUTE_BYTES: usize = 6;
223    if num_attributes_in_decoder == 0
224        || num_attributes_in_decoder > remaining_bytes / MIN_ATTRIBUTE_BYTES
225    {
226        return Err(DracoError::general(
227            "Invalid number of attributes".to_string(),
228        ));
229    }
230    Ok(())
231}
232
233#[cfg(feature = "point_cloud_decode")]
234fn validate_num_components(num_components: u8) -> Result<(), DracoError> {
235    if num_components == 0 {
236        return Err(DracoError::general(
237            "Invalid attribute component count".to_string(),
238        ));
239    }
240    Ok(())
241}
242
243impl PointCloudDecoder {
244    /// Creates a point cloud decoder with default state.
245    pub fn new() -> Self {
246        Self {
247            geometry_type: EncodedGeometryType::PointCloud,
248            #[cfg(feature = "point_cloud_decode")]
249            method: 0,
250            #[cfg(feature = "point_cloud_decode")]
251            flags: 0,
252            version_major: 0,
253            version_minor: 0,
254            #[cfg(feature = "point_cloud_decode")]
255            threads: 1,
256        }
257    }
258
259    /// Caps the threads a decode may use: `1`, the default, keeps everything on
260    /// the calling thread, `0` is as many as the machine has, and no count is
261    /// taken past sixteen. One by default because a library cannot see what
262    /// its caller already runs: a server decoding on a pool of its own would
263    /// have every decode multiply its threads by up to sixteen, and a hostile
264    /// stream would have the machine's cores by default. A thread the system
265    /// refuses to start is one fewer rather than an error.
266    /// [`MeshDecoder::set_threads`] passes the same cap to the point-cloud
267    /// streams a mesh decoder reads.
268    ///
269    /// [`MeshDecoder::set_threads`]: crate::MeshDecoder::set_threads
270    ///
271    /// A sequential point cloud with many attributes decodes them side by side
272    /// once the stream is large enough to repay it, and the decoded cloud is the
273    /// one a single thread produces, value for value. A large cloud of many
274    /// attributes, a Gaussian splat for one, decodes several times faster on
275    /// many threads. A cloud of two attributes, a scan's position and colour,
276    /// gains next to nothing: its decode is the position's one chain. On one
277    /// thread the attributes are decoded in order. On WebAssembly there are no
278    /// threads and the value is ignored.
279    #[cfg(feature = "point_cloud_decode")]
280    pub fn set_threads(&mut self, threads: i32) {
281        self.threads = threads;
282    }
283
284    /// The packed bitstream version (`0xMMmm`), `0` before a header was read.
285    ///
286    /// The attribute decoders read it when they bind prediction parents, which
287    /// is upstream's `decoder_->bitstream_version()` inside
288    /// `InitPredictionScheme`.
289    pub(crate) fn bitstream_version(&self) -> u16 {
290        crate::version::bitstream_version(self.version_major, self.version_minor)
291    }
292
293    /// Carries the version to a decoder that did not read the header itself.
294    ///
295    /// The mesh path parses its own header and then hands attributes to these
296    /// decoders through a `PointCloudDecoder` it constructs on the spot, so
297    /// without this that decoder reports version zero -- and every parent
298    /// binding on the fallback path would read as pre-2.0.
299    pub(crate) fn set_bitstream_version(&mut self, major: u8, minor: u8) {
300        self.version_major = major;
301        self.version_minor = minor;
302    }
303
304    #[cfg(feature = "point_cloud_decode")]
305    /// Decodes a Draco point cloud from `in_buffer` into `out_pc`.
306    ///
307    /// `out_pc` need not be empty: whatever it held is replaced, the same way
308    /// [`MeshDecoder::decode`](crate::mesh_decoder::MeshDecoder::decode)
309    /// replaces its mesh. `decode_after_header` does not clear, because its
310    /// caller has already done so and has decoded metadata since.
311    ///
312    /// # Errors
313    ///
314    /// Returns an error if the header is invalid, the bitstream version is
315    /// unsupported, or the encoded attributes are malformed.
316    pub fn decode(&mut self, in_buffer: &mut DecoderBuffer, out_pc: &mut PointCloud) -> Status {
317        out_pc.clear();
318
319        // 1. Decode Header
320        self.decode_header(in_buffer)?;
321
322        if version_at_least(
323            self.version_major,
324            self.version_minor,
325            VERSION_FLAGS_INTRODUCED,
326        ) && (self.flags & crate::metadata::METADATA_FLAG_MASK) != 0
327        {
328            let metadata = crate::metadata::GeometryMetadata::decode(in_buffer)
329                .map_err(|_| DracoError::general("Failed to decode metadata".to_string()))?;
330            out_pc.set_metadata(Some(metadata));
331        }
332
333        // 2. Decode Geometry Data
334        self.decode_geometry_data(in_buffer, out_pc)
335    }
336
337    /// Decode point cloud data when header + metadata have already been parsed.
338    /// Used by MeshDecoder to delegate point cloud streams.
339    #[cfg(feature = "point_cloud_decode")]
340    pub fn decode_after_header(
341        &mut self,
342        version_major: u8,
343        version_minor: u8,
344        method: u8,
345        buffer: &mut DecoderBuffer,
346        out_pc: &mut PointCloud,
347    ) -> Status {
348        self.version_major = version_major;
349        self.version_minor = version_minor;
350        self.method = method;
351        self.flags = 0;
352        self.geometry_type = EncodedGeometryType::PointCloud;
353        self.decode_geometry_data(buffer, out_pc)
354    }
355
356    #[cfg(feature = "point_cloud_decode")]
357    fn decode_header(&mut self, buffer: &mut DecoderBuffer) -> Status {
358        let mut magic = [0u8; 5];
359        buffer.decode_bytes(&mut magic)?;
360        if &magic != b"DRACO" {
361            return Err(DracoError::general("Invalid magic".to_string()));
362        }
363
364        self.version_major = buffer.decode_u8()?;
365        self.version_minor = buffer.decode_u8()?;
366        buffer.set_version(self.version_major, self.version_minor);
367
368        let g_type = buffer.decode_u8()?;
369        self.geometry_type = match g_type {
370            0 => EncodedGeometryType::PointCloud,
371            1 => EncodedGeometryType::TriangularMesh,
372            _ => return Err(DracoError::general("Invalid geometry type".to_string())),
373        };
374        if self.geometry_type != EncodedGeometryType::PointCloud {
375            return Err(DracoError::general(
376                "PointCloudDecoder cannot decode mesh bitstreams".to_string(),
377            ));
378        }
379
380        self.method = buffer.decode_u8()?;
381
382        // Flags field is always present in the binary header (C++ reads unconditionally).
383        self.flags = buffer
384            .decode_u16()
385            .map_err(|_| DracoError::general("Failed to decode flags".to_string()))?;
386
387        Ok(())
388    }
389
390    #[cfg(feature = "point_cloud_decode")]
391    fn decode_geometry_data(&mut self, buffer: &mut DecoderBuffer, pc: &mut PointCloud) -> Status {
392        let bitstream_version: u16 =
393            crate::version::bitstream_version(self.version_major, self.version_minor);
394        // Note: Draco point cloud bitstreams encode the number of points as a
395        // fixed-width int32 for both sequential (method=0) and KD-tree
396        // (method=1) encodings (see C++ PointCloudSequentialDecoder and
397        // PointCloudKdTreeDecoder). It is NOT varint encoded, even for v2.x.
398        // Read as the `int32_t` upstream reads, and refused when negative for
399        // the same reason `PointCloudSequentialDecoder` and
400        // `PointCloudKdTreeDecoder` refuse it: no encoder writes a count with
401        // the sign bit set, and C++ Draco stops on one before it allocates
402        // anything. Taking the same bytes unsigned is how a header claiming
403        // 2,147,483,652 points reached the KD-tree walk, which then expanded a
404        // single run-length node into 2.4 GB -- an OOM the `decode_drc` soak
405        // found, on a file upstream rejects in a fifth of a second having
406        // touched no memory at all.
407        //
408        // Past this the count is used but not guarded, which is also upstream's
409        // shape: what bounds the work is the allocation budget applied where
410        // the buffers are sized -- see `decode_budget`.
411        let declared_points = buffer.decode_u32()? as i32;
412        if declared_points < 0 {
413            return Err(DracoError::general(format!(
414                "Point cloud declares {declared_points} points"
415            )));
416        }
417        let num_points: usize = declared_points as usize;
418        buffer.check_points(num_points)?;
419        pc.set_num_points(num_points);
420
421        let num_attributes_decoders = buffer.decode_u8()? as usize;
422
423        if self.method == 1 {
424            // KD-tree encoding.
425            for _ in 0..num_attributes_decoders {
426                let mut att_decoder = KdTreeAttributesDecoder::new(0);
427                att_decoder
428                    .decode_attributes_decoder_data(pc, buffer)
429                    .map_err(|err| err.context("Failed to decode attribute metadata"))?;
430                att_decoder
431                    .decode_attributes(pc, buffer)
432                    .map_err(|err| err.context("Failed to decode attributes"))?;
433            }
434        } else {
435            // Sequential encoding.
436            struct AttributeSpec {
437                att_type: GeometryAttributeType,
438                data_type: DataType,
439                num_components: u8,
440                normalized: bool,
441                unique_id: u32,
442            }
443
444            for _ in 0..num_attributes_decoders {
445                let num_attributes_in_decoder: usize = if bitstream_version < 0x0200 {
446                    buffer.decode_u32()? as usize
447                } else {
448                    buffer.decode_varint()? as usize
449                };
450                if num_attributes_in_decoder == 0 {
451                    return Err(DracoError::general(
452                        "Invalid number of attributes".to_string(),
453                    ));
454                }
455                validate_num_attributes_in_decoder(
456                    num_attributes_in_decoder,
457                    buffer.remaining_size(),
458                )?;
459
460                let mut attribute_specs: Vec<AttributeSpec> =
461                    Vec::with_capacity(num_attributes_in_decoder);
462                let mut att_ids: Vec<i32> = Vec::with_capacity(num_attributes_in_decoder);
463                let mut decoder_types: Vec<u8> = Vec::with_capacity(num_attributes_in_decoder);
464                let mut pending_quant: Vec<PendingQuant> = Vec::new();
465                let mut pending_normals: Vec<PendingNormal> = Vec::new();
466
467                for _ in 0..num_attributes_in_decoder {
468                    let att_type_val = buffer.decode_u8()?;
469                    let att_type = GeometryAttributeType::try_from(att_type_val)?;
470
471                    let data_type_val = buffer.decode_u8()?;
472                    let data_type = DataType::try_from(data_type_val)?;
473
474                    let num_components = buffer.decode_u8()?;
475                    validate_num_components(num_components)?;
476                    let normalized = buffer.decode_u8()? != 0;
477                    let unique_id: u32 = if bitstream_version < 0x0103 {
478                        buffer.decode_u16()? as u32
479                    } else {
480                        buffer.decode_varint()? as u32
481                    };
482
483                    attribute_specs.push(AttributeSpec {
484                        att_type,
485                        data_type,
486                        num_components,
487                        normalized,
488                        unique_id,
489                    });
490                }
491
492                for _ in 0..num_attributes_in_decoder {
493                    decoder_types.push(buffer.decode_u8()?);
494                }
495
496                for (local_i, spec) in attribute_specs.iter().enumerate() {
497                    if decoder_types[local_i] == 0 {
498                        let entry_size =
499                            spec.num_components as usize * spec.data_type.byte_length();
500                        let bytes_needed = entry_size.checked_mul(num_points).ok_or_else(|| {
501                            DracoError::general(
502                                "Raw point cloud attribute byte count overflow".to_string(),
503                            )
504                        })?;
505                        if buffer.remaining_size() < bytes_needed {
506                            return Err(DracoError::general(
507                                "Not enough data for raw point cloud attribute values".to_string(),
508                            ));
509                        }
510                    }
511
512                    buffer.admit_attribute(
513                        num_points,
514                        spec.num_components as usize,
515                        spec.data_type.byte_length(),
516                    )?;
517                    let mut att = PointAttribute::new();
518                    // Nothing is charged against the *budget* for this
519                    // attribute, because nothing is taken for it: the buffer is
520                    // left unreserved and sized by whichever decoder writes the
521                    // values, once they exist. A charge there would be for an
522                    // allocation that no longer happens, and it is not free --
523                    // the budget is a backstop against unbacked reservations,
524                    // and billing it for backed ones is what made it refuse
525                    // files this crate writes. The caller's ceiling above is
526                    // the other question and is charged: it bounds what the
527                    // decode may produce at all, backed or not.
528                    att.init_deferred(
529                        spec.att_type,
530                        spec.num_components,
531                        spec.data_type,
532                        spec.normalized,
533                        num_points,
534                    )?;
535                    att.set_unique_id(spec.unique_id);
536                    let att_id = pc.add_attribute_preserve_unique_id(att);
537                    att_ids.push(att_id);
538                }
539
540                // The identity, and not written out. Entry `i` is point `i`
541                // here, so materializing it bought nothing and cost four bytes
542                // per point of a count the header supplies -- 134 MB from a
543                // 9 KB stream on one fuzz artifact, and gigabytes on a bigger
544                // claim. See `EntryToPointIdMap::Identity`.
545                let point_ids = if decoder_types.iter().any(|&decoder_type| decoder_type != 0) {
546                    Some(EntryToPointIdMap::identity(num_points))
547                } else {
548                    None
549                };
550
551                // On threads, attributes whose streams can be stepped over are
552                // decoded side by side, two at a time on each thread; on one,
553                // and where that cannot be done or goes wrong in any way, every
554                // attribute is decoded in order, two consecutive ones' symbols
555                // decoded together where they pair. On one thread the order is
556                // the faster of the two: the side-by-side decode gives each
557                // attribute a cloud and a buffer of its own, which the decode in
558                // order does not pay.
559                let threads = parallel::resolve(self.threads);
560                #[cfg(test)]
561                let in_order = IN_ORDER.with(|in_order| in_order.get());
562                #[cfg(not(test))]
563                let in_order = false;
564                let in_parallel = bitstream_version >= 0x0200
565                    && threads > 1
566                    && !in_order
567                    && num_points.saturating_mul(num_attributes_in_decoder)
568                        >= crate::parallel::ATTRIBUTES_MIN_VALUES
569                    && buffer.remaining_size() >= PARALLEL_MIN_STREAM_BYTES
570                    && decoder_types
571                        .iter()
572                        .filter(|&&decoder_type| (1..=3).contains(&decoder_type))
573                        .count()
574                        >= 2;
575                let decoded_in_parallel = in_parallel
576                    && self.decode_attributes_in_parallel(
577                        pc,
578                        buffer,
579                        &att_ids,
580                        &decoder_types,
581                        point_ids,
582                        num_points,
583                        threads,
584                        &mut pending_quant,
585                        &mut pending_normals,
586                    )?;
587                if !decoded_in_parallel {
588                    // Symbols decoded beside the attribute before, for the one
589                    // at hand.
590                    let mut ahead: Option<Vec<u32>> = None;
591                    for (local_i, &att_id) in att_ids.iter().enumerate() {
592                        let decoder_type = decoder_types[local_i];
593                        let mut predecoded = ahead.take();
594                        if predecoded.is_none() && bitstream_version >= 0x0200 {
595                            if let (Some(&next_id), Some(&next_type)) =
596                                (att_ids.get(local_i + 1), decoder_types.get(local_i + 1))
597                            {
598                                if let Some((first, second)) = Self::pair_symbols(
599                                    pc,
600                                    buffer,
601                                    (att_id, decoder_type),
602                                    (next_id, next_type),
603                                    num_points,
604                                ) {
605                                    predecoded = Some(first);
606                                    ahead = Some(second);
607                                }
608                            }
609                        }
610                        self.decode_attribute_in_place(
611                            pc,
612                            buffer,
613                            att_id,
614                            decoder_type,
615                            point_ids,
616                            num_points,
617                            &mut pending_quant,
618                            &mut pending_normals,
619                            predecoded,
620                        )?;
621                    }
622                }
623
624                for (local_i, &att_id) in att_ids.iter().enumerate() {
625                    match decoder_types[local_i] {
626                        2 if bitstream_version >= 0x0200 => {
627                            let idx = pending_quant
628                                .iter()
629                                .position(|p| p.att_id == att_id)
630                                .ok_or_else(|| {
631                                    DracoError::general(
632                                        "Missing pending quantized attribute transform".to_string(),
633                                    )
634                                })?;
635                            let original = pc.try_attribute(att_id)?;
636                            pending_quant[idx]
637                                .transform
638                                .decode_parameters(original, buffer)
639                                .map_err(|e| {
640                                    DracoError::general(format!(
641                                        "Failed to decode quantization parameters: {e}"
642                                    ))
643                                })?;
644                        }
645                        3 if bitstream_version >= 0x0200 => {
646                            let idx = pending_normals
647                                .iter()
648                                .position(|p| p.att_id == att_id)
649                                .ok_or_else(|| {
650                                    DracoError::general(
651                                        "Missing pending normal attribute transform".to_string(),
652                                    )
653                                })?;
654                            let quantization_bits = buffer.decode_u8()?;
655                            if !AttributeOctahedronTransform::is_valid_quantization_bits(
656                                quantization_bits as i32,
657                            ) {
658                                return Err(DracoError::general(
659                                    "Invalid normal quantization bits".to_string(),
660                                ));
661                            }
662                            pending_normals[idx].quantization_bits = quantization_bits;
663                        }
664                        _ => {}
665                    }
666                }
667
668                if in_parallel {
669                    dequantize_in_parallel(pc, pending_quant, threads)?;
670                    undo_octahedra_in_parallel(pc, pending_normals, threads)?;
671                } else {
672                    for q in pending_quant {
673                        let dst = pc.try_attribute_mut(q.att_id)?;
674                        q.transform
675                            .inverse_transform_attribute(&q.portable, dst)
676                            .map_err(|e| {
677                                DracoError::general(format!("Failed to dequantize attribute: {e}"))
678                            })?;
679                    }
680                    for n in pending_normals {
681                        let mut oct = AttributeOctahedronTransform::new(-1);
682                        oct.set_parameters(n.quantization_bits as i32)?;
683                        let dst = pc.try_attribute_mut(n.att_id)?;
684                        oct.inverse_transform_attribute(&n.portable, dst)
685                            .map_err(|e| {
686                                DracoError::general(format!("Failed to decode normals: {e}"))
687                            })?;
688                    }
689                }
690            }
691        }
692
693        Ok(())
694    }
695
696    /// Returns the encoded geometry type handled by this decoder.
697    pub fn get_geometry_type(&self) -> EncodedGeometryType {
698        self.geometry_type
699    }
700}
701
702#[cfg(feature = "point_cloud_decode")]
703impl PointCloudDecoder {
704    /// The symbols of two consecutive attributes' integer streams, decoded
705    /// side by side ([`decode_raw_symbol_pair_in`]), for each attribute's
706    /// decode to take instead of decoding its own. The buffer is left where it
707    /// stood, at the first attribute's stream, which is then decoded as ever.
708    ///
709    /// Where the second stream starts is found by stepping over the first
710    /// without decoding it. `None` where either attribute's stream is not one
711    /// that can be stepped over or paired -- tagged symbols, symbols too wide
712    /// for the paired loop, a prediction other than none or a plain
713    /// difference, a decoder that is not an integer one -- or where anything
714    /// goes wrong; the budget is then put back as it was and the two are
715    /// decoded one after the other, which is what reports the error if there
716    /// is one.
717    ///
718    /// A normal's stream codes its two octahedral coordinates, not the
719    /// attribute's three components.
720    fn pair_symbols(
721        pc: &PointCloud,
722        buffer: &mut DecoderBuffer,
723        first: (i32, u8),
724        second: (i32, u8),
725        num_points: usize,
726    ) -> Option<(Vec<u32>, Vec<u32>)> {
727        #[cfg(test)]
728        if PAIRS_OFF.with(|off| off.get()) {
729            return None;
730        }
731        let coded = |(att_id, decoder_type): (i32, u8)| -> Option<(usize, usize)> {
732            if !(1..=3).contains(&decoder_type) {
733                return None;
734            }
735            let components = coded_components(decoder_type, pc.try_attribute(att_id).ok()?);
736            Some((num_points.checked_mul(components)?, components))
737        };
738        // The paired loop runs on steps, which only a table of at most 2^16
739        // slots has: raw symbols of at most 11 bits. Past that each run would
740        // be decoded alone after its table had been read twice, so the two
741        // bytes that say so are read first, from a buffer of their own.
742        let pairs = |at: &DecoderBuffer| -> bool {
743            let Ok(mut look) = at.fork_at(at.position()) else {
744                return false;
745            };
746            matches!(
747                (look.decode_u8(), look.decode_u8()),
748                (Ok(1), Ok(bits)) if (1..=11).contains(&bits)
749            )
750        };
751        let (first, second) = (coded(first)?, coded(second)?);
752        let start = buffer.position();
753        let budget = buffer.budget();
754        let mut attempt = || -> Option<(Vec<u32>, Vec<u32>)> {
755            if !SequentialIntegerAttributeDecoder::seek_symbols(buffer) || !pairs(buffer) {
756                return None;
757            }
758            let first_symbols = buffer.position();
759            buffer.set_position(start).ok()?;
760            if !SequentialIntegerAttributeDecoder::skip_values(first.0, first.1, buffer).ok()? {
761                return None;
762            }
763            let mut probe = buffer.fork_at(buffer.position()).ok()?;
764            if !SequentialIntegerAttributeDecoder::seek_symbols(&mut probe) || !pairs(&probe) {
765                return None;
766            }
767            let second_symbols = probe.position();
768            buffer.set_position(first_symbols).ok()?;
769            decode_raw_symbol_pair_in(buffer, first, (second_symbols, second.0, second.1))
770        };
771        let symbols = attempt();
772        // `start` is a position this buffer held a moment ago.
773        let _ = buffer.set_position(start);
774        if symbols.is_none() {
775            buffer.restore_budget(budget);
776        }
777        #[cfg(test)]
778        if symbols.is_some() {
779            PAIRED.with(|paired| paired.set(paired.get() + 2));
780        }
781        symbols
782    }
783
784    /// Decodes one attribute's values from `buffer`, in place, the way the
785    /// sequential decoder always has.
786    #[allow(clippy::too_many_arguments)]
787    fn decode_attribute_in_place(
788        &self,
789        pc: &mut PointCloud,
790        buffer: &mut DecoderBuffer,
791        att_id: i32,
792        decoder_type: u8,
793        point_ids: Option<EntryToPointIdMap<'_>>,
794        num_points: usize,
795        pending_quant: &mut Vec<PendingQuant>,
796        pending_normals: &mut Vec<PendingNormal>,
797        symbols: Option<Vec<u32>>,
798    ) -> Status {
799        let bitstream_version = self.bitstream_version();
800        match decoder_type {
801            1 => {
802                let point_ids = point_ids.ok_or_else(|| {
803                    DracoError::general(
804                        "Point ids missing for integer attribute decoder".to_string(),
805                    )
806                })?;
807                let mut att_decoder = SequentialIntegerAttributeDecoder::new();
808                att_decoder.init(self, att_id);
809                if let Some(symbols) = symbols {
810                    att_decoder.set_predecoded_symbols(symbols);
811                }
812                att_decoder
813                    .decode_values(pc, point_ids, buffer, None, None, None, None, None, None)?;
814            }
815            2 => {
816                let mut att_decoder = SequentialQuantizationAttributeDecoder::new();
817                att_decoder.init(self, pc, att_id)?;
818                if let Some(symbols) = symbols {
819                    att_decoder.set_predecoded_symbols(symbols);
820                }
821                let portable = att_decoder.decode_values(
822                    pc,
823                    point_ids.ok_or_else(|| {
824                        DracoError::general(
825                            "Point ids missing for quantized attribute decoder".to_string(),
826                        )
827                    })?,
828                    buffer,
829                    bitstream_version,
830                    PortableExtent::Declared(num_points),
831                    None,
832                    None,
833                    None,
834                    None,
835                )?;
836                pending_quant.push(PendingQuant {
837                    att_id,
838                    portable,
839                    transform: att_decoder.into_transform(),
840                });
841            }
842            3 => {
843                let mut att_decoder = SequentialNormalAttributeDecoder::new();
844                att_decoder.init(self, pc, att_id)?;
845                if let Some(symbols) = symbols {
846                    att_decoder.set_predecoded_symbols(symbols);
847                }
848                let portable = att_decoder.decode_values(
849                    pc,
850                    point_ids.ok_or_else(|| {
851                        DracoError::general(
852                            "Point ids missing for normal attribute decoder".to_string(),
853                        )
854                    })?,
855                    buffer,
856                    bitstream_version,
857                    PortableExtent::Declared(num_points),
858                    None,
859                    None,
860                    None,
861                    None,
862                )?;
863                pending_normals.push(PendingNormal {
864                    att_id,
865                    portable,
866                    quantization_bits: att_decoder.quantization_bits(),
867                });
868            }
869            0 => {
870                // The identity map costs nothing to build and is all this
871                // decoder reads off it -- the values are copied verbatim, in
872                // order -- so the arm does not need the shared `point_ids`,
873                // which is `None` when every attribute is generic.
874                let mut att_decoder = SequentialGenericAttributeDecoder::new();
875                att_decoder.init(self, att_id);
876                att_decoder.decode_values(pc, EntryToPointIdMap::identity(num_points), buffer)?;
877            }
878            _ => {
879                return Err(DracoError::general(format!(
880                    "Unsupported sequential decoder type: {}",
881                    decoder_type
882                )));
883            }
884        }
885        Ok(())
886    }
887
888    /// Decodes the attributes of a 2.0 or later sequential stream side by side.
889    ///
890    /// The streams are laid end to end with no table of where each starts, so
891    /// the main thread walks them once, stepping over the ones whose symbols it
892    /// can step over (the raw scheme, which is nearly all of them) and decoding
893    /// the rest in place. Each stretch it steps over becomes a job that decodes
894    /// it as a buffer of its own. A stream that does not come out exactly the
895    /// length it was stepped over as is a stream this did not understand.
896    ///
897    /// Jobs are run two at a time, their symbols decoded in one loop
898    /// (`Job::run_pair`): the rANS run is a chain each symbol waits on, and two
899    /// of them overlap. Each job goes to a worker as soon as the walk reaches
900    /// it, so the walk, and the in-place decoding that is most of it for a
901    /// cloud of a few attributes, goes on while the workers run. Only for more
902    /// than one thread: on one the decode in order, which pairs too, is the
903    /// faster.
904    ///
905    /// `Ok(true)` when every attribute is decoded and `buffer` stands after the
906    /// last of them. `Ok(false)` when anything at all went wrong -- the position
907    /// and the pending lists are put back and the caller decodes in order, which
908    /// is what reports the error if there is one. So the result is the serial
909    /// decode's, value for value, and only the time differs.
910    #[allow(clippy::too_many_arguments)]
911    fn decode_attributes_in_parallel(
912        &self,
913        pc: &mut PointCloud,
914        buffer: &mut DecoderBuffer,
915        att_ids: &[i32],
916        decoder_types: &[u8],
917        point_ids: Option<EntryToPointIdMap<'_>>,
918        num_points: usize,
919        threads: usize,
920        pending_quant: &mut Vec<PendingQuant>,
921        pending_normals: &mut Vec<PendingNormal>,
922    ) -> Result<bool, DracoError> {
923        let start = buffer.position();
924        let stream = buffer.remaining_data();
925        let template = buffer.child_template();
926
927        let (walked, results) = {
928            let queue = std::sync::Mutex::new(None::<std::sync::mpsc::Receiver<Job>>);
929            let (sender, receiver) = std::sync::mpsc::channel::<Job>();
930            *queue.lock().unwrap() = Some(receiver);
931
932            let work = || {
933                let mut done = Vec::new();
934                loop {
935                    // Two at a time where two are waiting, taken together so
936                    // that no other worker splits them. A worker never waits
937                    // for the second: the walk may be decoding a long stream in
938                    // place, and the first job is better started alone than
939                    // held until the walk moves on.
940                    let (first, second) = {
941                        let guard = queue.lock().unwrap();
942                        let receiver = guard.as_ref().expect("the queue is open");
943                        let Ok(first) = receiver.recv() else { break };
944                        (first, receiver.try_recv().ok())
945                    };
946                    let index = first.index;
947                    match second {
948                        Some(second) => {
949                            let second_index = second.index;
950                            let [a, b] =
951                                Job::run_pair(first, second, self, &template, stream, num_points);
952                            done.push((index, a));
953                            done.push((second_index, b));
954                        }
955                        None => {
956                            done.push((index, first.run(self, &template, stream, num_points, None)))
957                        }
958                    }
959                }
960                done
961            };
962
963            // The walk, on this thread, and the workers beside it, no more of
964            // them than there are attributes to hand out. A failure in the walk
965            // is not returned from inside the scope: the workers have to be let
966            // go of first, and their results, if any, thrown away.
967            std::thread::scope(|scope| {
968                let workers: Vec<_> = (0..threads.min(att_ids.len()))
969                    .map_while(|_| std::thread::Builder::new().spawn_scoped(scope, work).ok())
970                    .collect();
971
972                let walked = self.walk_streams(
973                    pc,
974                    buffer,
975                    att_ids,
976                    decoder_types,
977                    point_ids,
978                    num_points,
979                    pending_quant,
980                    pending_normals,
981                    start,
982                    |job| sender.send(job).is_ok(),
983                );
984                // Nothing more is coming: the workers run out of jobs and
985                // finish, with this thread working beside them now that its
986                // walk is done -- alone, if the system started none of them.
987                drop(sender);
988                let mut finished = vec![work()];
989                finished.extend(workers.into_iter().map(|worker| {
990                    worker
991                        .join()
992                        .unwrap_or_else(|panic| std::panic::resume_unwind(panic))
993                }));
994                let mut results: Vec<Option<Result<Decoded, DracoError>>> = Vec::new();
995                for done in finished {
996                    for (index, result) in done {
997                        if results.len() <= index {
998                            results.resize_with(index + 1, || None);
999                        }
1000                        results[index] = Some(result);
1001                    }
1002                }
1003                (walked, results)
1004            })
1005        };
1006
1007        let give_up = |buffer: &mut DecoderBuffer,
1008                       pending_quant: &mut Vec<PendingQuant>,
1009                       pending_normals: &mut Vec<PendingNormal>|
1010         -> Result<bool, DracoError> {
1011            // The budget as it stood before the walk, which the decode in
1012            // order that follows charges the walk's own attributes to again.
1013            buffer.rejoin(&template, false);
1014            buffer.set_position(start)?;
1015            pending_quant.clear();
1016            pending_normals.clear();
1017            Ok(false)
1018        };
1019        let jobs = match walked {
1020            Ok(Some(count)) => count,
1021            _ => return give_up(buffer, pending_quant, pending_normals),
1022        };
1023        if jobs == 0 {
1024            buffer.rejoin(&template, true);
1025            return Ok(true);
1026        }
1027        let mut decoded_jobs = Vec::with_capacity(jobs);
1028        for result in results {
1029            match result {
1030                Some(Ok(decoded)) => decoded_jobs.push(decoded),
1031                _ => return give_up(buffer, pending_quant, pending_normals),
1032            }
1033        }
1034        // Every charge, the walk's and the pieces', was checked against what
1035        // all of them had spent, so what is left to check is the count.
1036        if decoded_jobs.len() != jobs {
1037            return give_up(buffer, pending_quant, pending_normals);
1038        }
1039        buffer.rejoin(&template, true);
1040        #[cfg(test)]
1041        ENGAGED.with(|engaged| engaged.set(engaged.get() + jobs));
1042        for decoded in decoded_jobs {
1043            match decoded {
1044                Decoded::Values { att_id, attribute } => *pc.try_attribute_mut(att_id)? = attribute,
1045                Decoded::Quantized {
1046                    att_id,
1047                    portable,
1048                    transform,
1049                } => pending_quant.push(PendingQuant {
1050                    att_id,
1051                    portable,
1052                    transform,
1053                }),
1054                Decoded::Normal {
1055                    att_id,
1056                    portable,
1057                    quantization_bits,
1058                } => pending_normals.push(PendingNormal {
1059                    att_id,
1060                    portable,
1061                    quantization_bits,
1062                }),
1063            }
1064        }
1065        Ok(true)
1066    }
1067
1068    /// The walk `decode_attributes_in_parallel` makes: each attribute stream
1069    /// stepped over becomes a job handed to `send`, in order, and each one that
1070    /// cannot be is decoded in place. `Ok(Some(jobs))` when the walk reached
1071    /// the end; `Ok(None)` when a stream decoded in place failed or `send`
1072    /// refused a job, which the caller answers by decoding everything in order.
1073    #[allow(clippy::too_many_arguments)]
1074    fn walk_streams(
1075        &self,
1076        pc: &mut PointCloud,
1077        buffer: &mut DecoderBuffer,
1078        att_ids: &[i32],
1079        decoder_types: &[u8],
1080        point_ids: Option<EntryToPointIdMap<'_>>,
1081        num_points: usize,
1082        pending_quant: &mut Vec<PendingQuant>,
1083        pending_normals: &mut Vec<PendingNormal>,
1084        start: usize,
1085        mut send: impl FnMut(Job) -> bool,
1086    ) -> Result<Option<usize>, DracoError> {
1087        let mut count = 0usize;
1088        for (local_i, &att_id) in att_ids.iter().enumerate() {
1089            let decoder_type = decoder_types[local_i];
1090            if (1..=3).contains(&decoder_type) {
1091                let before = buffer.position();
1092                let components = coded_components(decoder_type, pc.try_attribute(att_id)?);
1093                let skipped = match num_points.checked_mul(components) {
1094                    Some(values) => {
1095                        SequentialIntegerAttributeDecoder::skip_values(values, components, buffer)
1096                    }
1097                    None => Ok(false),
1098                };
1099                if matches!(skipped, Ok(true)) {
1100                    let job = Job {
1101                        index: count,
1102                        att_id,
1103                        decoder_type,
1104                        start: before - start,
1105                        end: buffer.position() - start,
1106                        seed: pc.try_attribute(att_id)?.clone(),
1107                    };
1108                    if !send(job) {
1109                        return Ok(None);
1110                    }
1111                    count += 1;
1112                    continue;
1113                }
1114                buffer.set_position(before)?;
1115            }
1116            if self
1117                .decode_attribute_in_place(
1118                    pc,
1119                    buffer,
1120                    att_id,
1121                    decoder_type,
1122                    point_ids,
1123                    num_points,
1124                    pending_quant,
1125                    pending_normals,
1126                    None,
1127                )
1128                .is_err()
1129            {
1130                return Ok(None);
1131            }
1132        }
1133        Ok(Some(count))
1134    }
1135}
1136
1137/// One attribute's stream, stepped over, waiting for a thread: where it lies in
1138/// the stream, and the attribute to decode it into.
1139#[cfg(feature = "point_cloud_decode")]
1140struct Job {
1141    index: usize,
1142    att_id: i32,
1143    decoder_type: u8,
1144    start: usize,
1145    end: usize,
1146    seed: PointAttribute,
1147}
1148
1149/// What a thread made of a [`Job`].
1150#[cfg(feature = "point_cloud_decode")]
1151enum Decoded {
1152    Values {
1153        att_id: i32,
1154        attribute: PointAttribute,
1155    },
1156    Quantized {
1157        att_id: i32,
1158        portable: PointAttribute,
1159        transform: AttributeQuantizationTransform,
1160    },
1161    Normal {
1162        att_id: i32,
1163        portable: PointAttribute,
1164        quantization_bits: u8,
1165    },
1166}
1167
1168#[cfg(feature = "point_cloud_decode")]
1169impl Job {
1170    /// Decodes the stretch of `stream` this job names, as a buffer of its own
1171    /// over an attribute of its own, and returns what it made and what it spent
1172    /// of the allocation budget.
1173    fn run(
1174        self,
1175        decoder: &PointCloudDecoder,
1176        template: &crate::decoder_buffer::ChildTemplate,
1177        stream: &[u8],
1178        num_points: usize,
1179        symbols: Option<Vec<u32>>,
1180    ) -> Result<Decoded, DracoError> {
1181        let bitstream_version = decoder.bitstream_version();
1182        let mut child = template.open(&stream[self.start..self.end]);
1183        let mut mini = PointCloud::new();
1184        mini.set_num_points(num_points);
1185        mini.add_attribute_preserve_unique_id(self.seed);
1186        let ids = EntryToPointIdMap::identity(num_points);
1187        let att_id = self.att_id;
1188        let decoded = match self.decoder_type {
1189            1 => {
1190                let mut att_decoder = SequentialIntegerAttributeDecoder::new();
1191                att_decoder.init(decoder, 0);
1192                if let Some(symbols) = symbols {
1193                    att_decoder.set_predecoded_symbols(symbols);
1194                }
1195                att_decoder.decode_values(
1196                    &mut mini, ids, &mut child, None, None, None, None, None, None,
1197                )?;
1198                Decoded::Values {
1199                    att_id,
1200                    attribute: std::mem::take(mini.try_attribute_mut(0)?),
1201                }
1202            }
1203            2 => {
1204                let mut att_decoder = SequentialQuantizationAttributeDecoder::new();
1205                att_decoder.init(decoder, &mini, 0)?;
1206                if let Some(symbols) = symbols {
1207                    att_decoder.set_predecoded_symbols(symbols);
1208                }
1209                let portable = att_decoder.decode_values(
1210                    &mut mini,
1211                    ids,
1212                    &mut child,
1213                    bitstream_version,
1214                    PortableExtent::Declared(num_points),
1215                    None,
1216                    None,
1217                    None,
1218                    None,
1219                )?;
1220                Decoded::Quantized {
1221                    att_id,
1222                    portable,
1223                    transform: att_decoder.into_transform(),
1224                }
1225            }
1226            _ => {
1227                let mut att_decoder = SequentialNormalAttributeDecoder::new();
1228                att_decoder.init(decoder, &mini, 0)?;
1229                if let Some(symbols) = symbols {
1230                    att_decoder.set_predecoded_symbols(symbols);
1231                }
1232                let portable = att_decoder.decode_values(
1233                    &mut mini,
1234                    ids,
1235                    &mut child,
1236                    bitstream_version,
1237                    PortableExtent::Declared(num_points),
1238                    None,
1239                    None,
1240                    None,
1241                    None,
1242                )?;
1243                Decoded::Normal {
1244                    att_id,
1245                    portable,
1246                    quantization_bits: att_decoder.quantization_bits(),
1247                }
1248            }
1249        };
1250        if child.remaining_size() != 0 {
1251            return Err(DracoError::general(
1252                "An attribute's stream is not the length it was stepped over as".to_string(),
1253            ));
1254        }
1255        Ok(decoded)
1256    }
1257
1258    /// Runs two jobs, their symbols decoded side by side first
1259    /// (`decode_raw_symbol_pair`) and each job then decoded with its own. Where
1260    /// the pair cannot be decoded together the jobs run as they would alone,
1261    /// so what comes of each is what `run` makes of it.
1262    fn run_pair(
1263        a: Job,
1264        b: Job,
1265        decoder: &PointCloudDecoder,
1266        template: &crate::decoder_buffer::ChildTemplate,
1267        stream: &[u8],
1268        num_points: usize,
1269    ) -> [Result<Decoded, DracoError>; 2] {
1270        let mut child_a = template.open(&stream[a.start..a.end]);
1271        let mut child_b = template.open(&stream[b.start..b.end]);
1272        let values = |job: &Job| {
1273            let components = coded_components(job.decoder_type, &job.seed);
1274            (num_points.checked_mul(components), components)
1275        };
1276        let ((values_a, components_a), (values_b, components_b)) = (values(&a), values(&b));
1277        let symbols = match (values_a, values_b) {
1278            (Some(values_a), Some(values_b))
1279                if SequentialIntegerAttributeDecoder::seek_symbols(&mut child_a)
1280                    && SequentialIntegerAttributeDecoder::seek_symbols(&mut child_b) =>
1281            {
1282                decode_raw_symbol_pair(
1283                    (&mut child_a, values_a, components_a),
1284                    (&mut child_b, values_b, components_b),
1285                )
1286            }
1287            _ => None,
1288        };
1289        #[cfg(test)]
1290        if symbols.is_some() {
1291            PAIRED.with(|paired| paired.set(paired.get() + 2));
1292        }
1293        let (symbols_a, symbols_b) = symbols.map_or((None, None), |(a, b)| (Some(a), Some(b)));
1294        [
1295            a.run(decoder, template, stream, num_points, symbols_a),
1296            b.run(decoder, template, stream, num_points, symbols_b),
1297        ]
1298    }
1299}
1300
1301#[cfg(all(test, feature = "point_cloud_decode", feature = "encoder"))]
1302mod parallel_tests {
1303    use super::*;
1304    use crate::encoder_buffer::EncoderBuffer;
1305    use crate::encoder_options::EncoderOptions;
1306    use crate::point_cloud_encoder::PointCloudEncoder;
1307
1308    struct Xorshift(u64);
1309
1310    impl Xorshift {
1311        fn next(&mut self) -> u64 {
1312            self.0 ^= self.0 << 13;
1313            self.0 ^= self.0 >> 7;
1314            self.0 ^= self.0 << 17;
1315            self.0
1316        }
1317
1318        fn unit(&mut self) -> f32 {
1319            (self.next() >> 40) as f32 / (1u64 << 24) as f32
1320        }
1321    }
1322
1323    fn float_attribute(
1324        kind: GeometryAttributeType,
1325        components: u8,
1326        values: &[f32],
1327    ) -> PointAttribute {
1328        let points = values.len() / components as usize;
1329        let mut attribute = PointAttribute::new();
1330        attribute.init(kind, components, DataType::Float32, false, points);
1331        for (i, value) in values.iter().enumerate() {
1332            attribute.buffer_mut().write(i * 4, &value.to_le_bytes());
1333        }
1334        attribute
1335    }
1336
1337    fn byte_attribute(
1338        kind: GeometryAttributeType,
1339        components: u8,
1340        values: &[u8],
1341    ) -> PointAttribute {
1342        let points = values.len() / components as usize;
1343        let mut attribute = PointAttribute::new();
1344        attribute.init(kind, components, DataType::Uint8, false, points);
1345        for (i, value) in values.iter().enumerate() {
1346            attribute.buffer_mut().write(i, &[*value]);
1347        }
1348        attribute
1349    }
1350
1351    /// Every kind of attribute stream a sequential cloud carries -- a position
1352    /// the coder writes tagged, normals, quantized floats, bytes, raw floats --
1353    /// with noise in all of them, so the stream is as large as the values.
1354    fn stream(points: usize, seed: u64) -> Vec<u8> {
1355        stream_with_threads(points, seed, 1)
1356    }
1357
1358    fn stream_with_threads(points: usize, seed: u64, threads: i32) -> Vec<u8> {
1359        let mut rng = Xorshift(seed);
1360        let mut cloud = PointCloud::new();
1361        cloud.set_num_points(points);
1362        let mut options = EncoderOptions::new();
1363        options.set_encoding_method(0);
1364        options.set_threads(threads);
1365        let mut next_id = 0;
1366        let mut add = |cloud: &mut PointCloud, attribute: PointAttribute, bits: i32| {
1367            cloud.add_attribute(attribute);
1368            if bits > 0 {
1369                options.set_attribute_int(next_id, "quantization_bits", bits);
1370            }
1371            next_id += 1;
1372        };
1373        let positions: Vec<f32> = (0..points * 3).map(|_| rng.unit() * 100.0).collect();
1374        add(
1375            &mut cloud,
1376            float_attribute(GeometryAttributeType::Position, 3, &positions),
1377            16,
1378        );
1379        let normals: Vec<f32> = (0..points * 3).map(|_| rng.unit() * 2.0 - 1.0).collect();
1380        add(
1381            &mut cloud,
1382            float_attribute(GeometryAttributeType::Normal, 3, &normals),
1383            8,
1384        );
1385        for _ in 0..40 {
1386            let values: Vec<f32> = (0..points).map(|_| rng.unit()).collect();
1387            add(
1388                &mut cloud,
1389                float_attribute(GeometryAttributeType::Generic, 1, &values),
1390                8,
1391            );
1392        }
1393        let colours: Vec<u8> = (0..points * 3).map(|_| (rng.next() >> 56) as u8).collect();
1394        add(
1395            &mut cloud,
1396            byte_attribute(GeometryAttributeType::Color, 3, &colours),
1397            0,
1398        );
1399        let tags: Vec<u8> = (0..points).map(|_| (rng.next() >> 56) as u8).collect();
1400        add(
1401            &mut cloud,
1402            byte_attribute(GeometryAttributeType::Generic, 1, &tags),
1403            0,
1404        );
1405        // No quantization on a float: the coder copies it as raw bytes.
1406        let raw: Vec<f32> = (0..points).map(|_| rng.unit()).collect();
1407        add(
1408            &mut cloud,
1409            float_attribute(GeometryAttributeType::Generic, 1, &raw),
1410            0,
1411        );
1412        // Attributes that never vary, as a 3DGS file's normals do not: their
1413        // alphabet is one symbol and no payload backs the count, so each run
1414        // is drawn from the allowance the limits admitted, which the threads
1415        // draw on together.
1416        for _ in 0..3 {
1417            let zeros = vec![0.0f32; points];
1418            add(
1419                &mut cloud,
1420                float_attribute(GeometryAttributeType::Generic, 1, &zeros),
1421                8,
1422            );
1423        }
1424        let same = vec![7u8; points];
1425        add(
1426            &mut cloud,
1427            byte_attribute(GeometryAttributeType::Generic, 1, &same),
1428            0,
1429        );
1430        let mut encoder = PointCloudEncoder::new();
1431        encoder.set_point_cloud(cloud);
1432        let mut buffer = EncoderBuffer::new();
1433        encoder.encode(&options, &mut buffer).expect("encodes");
1434        buffer.data().to_vec()
1435    }
1436
1437    /// What a decode makes of `bytes`: every attribute's bytes, or the error.
1438    fn decode(bytes: &[u8], threads: i32) -> Result<Vec<Vec<u8>>, String> {
1439        let mut decoder = PointCloudDecoder::new();
1440        decoder.set_threads(threads);
1441        let mut cloud = PointCloud::new();
1442        decoder
1443            .decode(&mut DecoderBuffer::new(bytes), &mut cloud)
1444            .map_err(|e| e.to_string())?;
1445        Ok((0..cloud.num_attributes())
1446            .map(|id| cloud.attribute(id).buffer().data().to_vec())
1447            .collect())
1448    }
1449
1450    /// The decode in order, attribute by attribute, that the side-by-side one
1451    /// is held to.
1452    fn decode_in_order(bytes: &[u8]) -> Result<Vec<Vec<u8>>, String> {
1453        IN_ORDER.with(|in_order| in_order.set(true));
1454        let decoded = decode(bytes, 1);
1455        IN_ORDER.with(|in_order| in_order.set(false));
1456        decoded
1457    }
1458
1459    fn engaged_by<T>(f: impl FnOnce() -> T) -> (T, usize) {
1460        let before = ENGAGED.with(|e| e.get());
1461        let out = f();
1462        (out, ENGAGED.with(|e| e.get()) - before)
1463    }
1464
1465    #[test]
1466    fn side_by_side_decodes_the_same_cloud_value_for_value() {
1467        let bytes = stream(40_000, 1);
1468        assert!(
1469            bytes.len() >= PARALLEL_MIN_STREAM_BYTES,
1470            "{} bytes",
1471            bytes.len()
1472        );
1473        let (serial, engaged) = engaged_by(|| decode_in_order(&bytes));
1474        assert_eq!(engaged, 0, "the reference took the side-by-side path");
1475        let serial = serial.expect("decodes");
1476        for threads in [1, 2, 5, 16] {
1477            let paired_before = PAIRED.with(|paired| paired.get());
1478            let (parallel, engaged) = engaged_by(|| decode(&bytes, threads));
1479            // One thread decodes in order, pairing as it goes; on the pairs'
1480            // own thread the count is kept, and every attribute is paired but
1481            // the ones whose symbols are too wide or tagged.
1482            if threads == 1 {
1483                let paired = PAIRED.with(|paired| paired.get()) - paired_before;
1484                assert_eq!(engaged, 0, "one thread took the side-by-side path");
1485                assert!(paired >= 40, "{paired} paired in order");
1486            } else {
1487                assert!(
1488                    engaged >= 44,
1489                    "the parallel path ran for {engaged} streams at {threads} threads"
1490                );
1491            }
1492            assert_eq!(parallel.expect("decodes"), serial, "{threads} threads");
1493        }
1494    }
1495
1496    /// The encoder runs each attribute's encoder on its own thread and appends
1497    /// their buffers in attribute order. That is the stream one buffer written in
1498    /// that order holds, so the bytes may not depend on the number of threads.
1499    #[test]
1500    fn the_encoded_stream_is_the_same_bytes_at_any_number_of_threads() {
1501        let single = stream_with_threads(40_000, 5, 1);
1502        for threads in [2, 7, 16, 0] {
1503            assert_eq!(
1504                stream_with_threads(40_000, 5, threads),
1505                single,
1506                "{threads} threads"
1507            );
1508        }
1509    }
1510
1511    /// A cloud of few attributes, each past a million values, gives each
1512    /// attribute threads of its own for its quantization, gather, prediction
1513    /// and symbol plan. The stream may not depend on how many, with or without
1514    /// the prediction search, which plans twice.
1515    #[test]
1516    fn an_attribute_split_across_threads_writes_the_same_bytes() {
1517        let points = 400_000;
1518        let mut rng = Xorshift(11);
1519        let mut cloud = PointCloud::new();
1520        cloud.set_num_points(points);
1521        // Coarse positions, so equal values and both zeros turn up in the
1522        // bounds a quantization folds.
1523        let positions: Vec<f32> = (0..points * 3)
1524            .map(|i| match i % 97 {
1525                0 => -0.0,
1526                1 => 0.0,
1527                _ => (rng.unit() * 64.0).floor() - 32.0,
1528            })
1529            .collect();
1530        cloud.add_attribute(float_attribute(
1531            GeometryAttributeType::Position,
1532            3,
1533            &positions,
1534        ));
1535        let colours: Vec<u8> = (0..points * 3).map(|_| (rng.next() >> 58) as u8).collect();
1536        cloud.add_attribute(byte_attribute(GeometryAttributeType::Color, 3, &colours));
1537        let encode = |threads: i32, search: bool| {
1538            let mut options = EncoderOptions::new();
1539            options.set_encoding_method(0);
1540            options.set_attribute_int(0, "quantization_bits", 14);
1541            options.set_threads(threads);
1542            options.set_prediction_search(search);
1543            let mut encoder = PointCloudEncoder::new();
1544            encoder.set_point_cloud(cloud.clone());
1545            let mut buffer = EncoderBuffer::new();
1546            encoder.encode(&options, &mut buffer).expect("encodes");
1547            buffer.data().to_vec()
1548        };
1549        for search in [false, true] {
1550            let single = encode(1, search);
1551            for threads in [4, 16] {
1552                assert!(
1553                    encode(threads, search) == single,
1554                    "{threads} threads, search {search}"
1555                );
1556            }
1557        }
1558    }
1559
1560    #[test]
1561    fn a_stream_too_small_to_repay_the_threads_is_decoded_in_place() {
1562        let bytes = stream(500, 2);
1563        let (parallel, engaged) = engaged_by(|| decode(&bytes, 16));
1564        assert_eq!(engaged, 0);
1565        assert_eq!(parallel, decode_in_order(&bytes));
1566    }
1567
1568    #[test]
1569    fn a_damaged_stream_is_read_by_the_threads_as_by_one() {
1570        let bytes = stream(40_000, 3);
1571        let mut rng = Xorshift(77);
1572        for trial in 0..40 {
1573            let mut damaged = bytes.clone();
1574            // Bytes anywhere, and bytes in the header and the attribute
1575            // descriptions, where the lengths come from.
1576            let at = if trial % 2 == 0 {
1577                rng.next() as usize % damaged.len()
1578            } else {
1579                rng.next() as usize % 400
1580            };
1581            damaged[at] ^= 1 << (rng.next() % 8);
1582            let serial = decode_in_order(&damaged);
1583            for threads in [1, 16] {
1584                let parallel = decode(&damaged, threads);
1585                assert_eq!(
1586                    parallel, serial,
1587                    "trial {trial}: byte {at} damaged, {threads} threads"
1588                );
1589            }
1590        }
1591        // And cut short.
1592        for cut in [bytes.len() / 2, bytes.len() - 1, bytes.len() - 40, 5000] {
1593            for threads in [1, 16] {
1594                assert_eq!(
1595                    decode(&bytes[..cut], threads),
1596                    decode_in_order(&bytes[..cut]),
1597                    "cut at {cut}, {threads} threads"
1598                );
1599            }
1600        }
1601    }
1602
1603    /// Attributes that never vary have runs no payload backs, one symbol's
1604    /// worth of stream for millions of values, and a big cloud has several.
1605    /// The allowance and the budget are one for the whole decode, so what the
1606    /// threads draw together is what one thread would, and such a cloud
1607    /// decodes on threads as it does in order.
1608    #[test]
1609    fn constant_attributes_in_a_big_cloud_do_not_exhaust_what_threads_share() {
1610        let points = 7_000_000;
1611        let mut rng = Xorshift(9);
1612        let mut cloud = PointCloud::new();
1613        cloud.set_num_points(points);
1614        let mut options = EncoderOptions::new();
1615        options.set_encoding_method(0);
1616        options.set_threads(1);
1617        let mut next_id = 0;
1618        let mut add = |cloud: &mut PointCloud, attribute: PointAttribute| {
1619            cloud.add_attribute(attribute);
1620            options.set_attribute_int(next_id, "quantization_bits", 8);
1621            next_id += 1;
1622        };
1623        for _ in 0..3 {
1624            let noise: Vec<f32> = (0..points).map(|_| rng.unit()).collect();
1625            add(
1626                &mut cloud,
1627                float_attribute(GeometryAttributeType::Generic, 1, &noise),
1628            );
1629        }
1630        for _ in 0..4 {
1631            let zeros = vec![0.0f32; points];
1632            add(
1633                &mut cloud,
1634                float_attribute(GeometryAttributeType::Generic, 1, &zeros),
1635            );
1636        }
1637        let mut encoder = PointCloudEncoder::new();
1638        encoder.set_point_cloud(cloud);
1639        let mut buffer = EncoderBuffer::new();
1640        encoder.encode(&options, &mut buffer).expect("encodes");
1641        let bytes = buffer.data();
1642        assert!(bytes.len() >= PARALLEL_MIN_STREAM_BYTES);
1643        let serial = decode_in_order(bytes).expect("decodes");
1644        for threads in [1, 4, 12, 16] {
1645            let (parallel, engaged) = engaged_by(|| decode(bytes, threads));
1646            assert_eq!(
1647                engaged,
1648                if threads > 1 { 7 } else { 0 },
1649                "{threads} threads: the parallel path ran for {engaged} of 7 streams"
1650            );
1651            assert_eq!(parallel.expect("decodes"), serial, "{threads} threads");
1652        }
1653    }
1654
1655    /// Constant attributes past what the allocation backstop holds, beside one
1656    /// that makes the stream long enough for the threads. A constant run is
1657    /// drawn from the values the caller's limits admitted, and the pieces
1658    /// decoded side by side draw on that one allowance as the decode in order
1659    /// does: the threads and the pairing run on every stream, rather than
1660    /// failing on the budget and leaving it all to one thread.
1661    #[test]
1662    fn constant_attributes_past_the_backstop_decode_side_by_side() {
1663        const CONSTANT: usize = 4;
1664        const COMPONENTS: usize = 3;
1665        const CEILING_IN_VALUES: usize =
1666            crate::decode_budget::MAX_UNBACKED_BYTES / std::mem::size_of::<u32>();
1667        // A quarter past the ceiling, so no payload the runs carry brings them
1668        // back under it.
1669        const POINTS: usize = CEILING_IN_VALUES / (CONSTANT * COMPONENTS) * 5 / 4;
1670        let mut cloud = PointCloud::new();
1671        cloud.set_num_points(POINTS);
1672        for _ in 0..CONSTANT {
1673            let colour = vec![0u8; POINTS * COMPONENTS];
1674            cloud.add_attribute(byte_attribute(
1675                GeometryAttributeType::Color,
1676                COMPONENTS as u8,
1677                &colour,
1678            ));
1679        }
1680        let mut rng = Xorshift(21);
1681        let noise: Vec<u8> = (0..POINTS).map(|_| (rng.next() >> 56) as u8).collect();
1682        cloud.add_attribute(byte_attribute(GeometryAttributeType::Generic, 1, &noise));
1683        let mut options = EncoderOptions::new();
1684        options.set_encoding_method(0);
1685        options.set_threads(1);
1686        let mut encoder = PointCloudEncoder::new();
1687        encoder.set_point_cloud(cloud);
1688        let mut buffer = EncoderBuffer::new();
1689        encoder.encode(&options, &mut buffer).expect("encodes");
1690        let bytes = buffer.data();
1691        assert!(bytes.len() >= PARALLEL_MIN_STREAM_BYTES);
1692        let serial = decode_in_order(bytes).expect("decodes");
1693        for threads in [1, 16] {
1694            let (parallel, engaged) = engaged_by(|| decode(bytes, threads));
1695            assert_eq!(
1696                engaged,
1697                if threads > 1 { CONSTANT + 1 } else { 0 },
1698                "{threads} threads: {engaged} streams decoded aside"
1699            );
1700            assert_eq!(parallel.expect("decodes"), serial, "{threads} threads");
1701        }
1702    }
1703
1704    /// Every synthetic shape, in its own order and shuffled, from no points to
1705    /// past the thresholds that hand a cloud to threads: the same bytes encoded
1706    /// on one thread and on several, the same values decoded in order, paired
1707    /// and side by side, and those values the source's -- to the bit where
1708    /// nothing was quantized, within half a step where it was.
1709    #[test]
1710    fn synthetic_clouds_come_back_the_same_by_every_path() {
1711        use crate::synthetic_cloud::{self, Cloud};
1712        let mut clouds: Vec<Cloud> = [0, 1, 2, 3, 97]
1713            .into_iter()
1714            .flat_map(|points| synthetic_cloud::all(points, 1))
1715            .collect();
1716        // Big enough that every stream but the constant cloud's passes
1717        // `PARALLEL_MIN_STREAM_BYTES`, so the threads and the pairing run.
1718        let big = synthetic_cloud::all(400_000, 2);
1719        clouds.extend(big.iter().map(|cloud| cloud.shuffled(3)));
1720        clouds.extend(big);
1721        std::thread::scope(|scope| {
1722            for cloud in &clouds {
1723                scope.spawn(|| every_path_agrees(cloud));
1724            }
1725        });
1726    }
1727
1728    fn every_path_agrees(cloud: &crate::synthetic_cloud::Cloud) {
1729        let name = format!("{} of {} points", cloud.name, cloud.points);
1730        let encode = |threads: i32| {
1731            let mut options = EncoderOptions::new();
1732            options.set_encoding_method(0);
1733            options.set_threads(threads);
1734            for (id, column) in cloud.columns.iter().enumerate() {
1735                if column.quantization_bits > 0 {
1736                    options.set_attribute_int(
1737                        id as i32,
1738                        "quantization_bits",
1739                        column.quantization_bits,
1740                    );
1741                }
1742            }
1743            let mut encoder = PointCloudEncoder::new();
1744            encoder.set_point_cloud(cloud.to_point_cloud());
1745            let mut buffer = EncoderBuffer::new();
1746            encoder
1747                .encode(&options, &mut buffer)
1748                .map(|()| buffer.data().to_vec())
1749                .map_err(|e| e.to_string())
1750        };
1751        let single = encode(1);
1752        for threads in [4, 16] {
1753            assert!(
1754                encode(threads) == single,
1755                "{name}: the stream on {threads} threads differs"
1756            );
1757        }
1758        // An empty attribute has no range to quantize over, and the encoder
1759        // says so rather than read a value that is not there.
1760        let bytes = match single {
1761            Err(e) if cloud.points == 0 && e.contains("empty attribute") => return,
1762            result => result.unwrap_or_else(|e| panic!("{name}: {e}")),
1763        };
1764        let decoded = decode_in_order(&bytes).unwrap_or_else(|e| panic!("{name}: {e}"));
1765        let big = cloud.points >= 400_000 && !cloud.name.starts_with("constant");
1766        assert!(
1767            !big || bytes.len() >= PARALLEL_MIN_STREAM_BYTES,
1768            "{name}: {} bytes are too few to send the decode to threads",
1769            bytes.len()
1770        );
1771        for threads in [1, 4, 16] {
1772            let paired = PAIRED.with(|paired| paired.get());
1773            let (parallel, engaged) = engaged_by(|| decode(&bytes, threads));
1774            let paired = PAIRED.with(|paired| paired.get()) - paired;
1775            assert!(
1776                parallel.as_ref() == Ok(&decoded),
1777                "{name}: the decode on {threads} threads differs"
1778            );
1779            assert!(
1780                !big || if threads > 1 {
1781                    engaged > 0
1782                } else {
1783                    engaged == 0
1784                },
1785                "{name}: {threads} threads decoded {engaged} streams aside, {paired} paired"
1786            );
1787        }
1788        for (column, got) in cloud.columns.iter().zip(&decoded) {
1789            let source = column.values.bytes();
1790            let got = &got[..source.len()];
1791            if column.quantization_bits == 0 {
1792                assert!(got == source, "{name}: {} changed", column.name);
1793                continue;
1794            }
1795            let values = |bytes: &[u8]| -> Vec<f32> {
1796                bytes
1797                    .as_chunks::<4>()
1798                    .0
1799                    .iter()
1800                    .map(|&b| f32::from_le_bytes(b))
1801                    .collect()
1802            };
1803            let (source, got) = (values(&source), values(got));
1804            if column.kind == GeometryAttributeType::Normal {
1805                // Octahedral coordinates on a grid of `bits` a side: a unit
1806                // vector comes back within a few cells. A zero one, which the
1807                // octahedron has no point for, comes back as some direction.
1808                let cell = 4.0 / (1u32 << column.quantization_bits) as f32;
1809                let pairs = source.as_chunks::<3>().0.iter().zip(got.as_chunks::<3>().0);
1810                for (i, (want, have)) in pairs.enumerate() {
1811                    if want.iter().all(|&v| v == 0.0) {
1812                        continue;
1813                    }
1814                    let error = want
1815                        .iter()
1816                        .zip(have)
1817                        .map(|(w, h)| (w - h).abs())
1818                        .fold(0.0, f32::max);
1819                    assert!(
1820                        error <= 2.0 * cell,
1821                        "{name}: normal {i} came back {have:?} for {want:?}"
1822                    );
1823                }
1824                continue;
1825            }
1826            // The quantizer's grid spans the widest component's range.
1827            let range = (0..column.components)
1828                .map(|c| {
1829                    let component = source.iter().skip(c).step_by(column.components);
1830                    let (lo, hi) =
1831                        component.fold((f32::MAX, f32::MIN), |(lo, hi), &v| (lo.min(v), hi.max(v)));
1832                    hi - lo
1833                })
1834                .fold(0.0f32, f32::max);
1835            let step = range / ((1u32 << column.quantization_bits) - 1) as f32;
1836            for (i, (&want, &have)) in source.iter().zip(&got).enumerate() {
1837                let slack = (want.abs() + range) * f32::EPSILON * 4.0;
1838                assert!(
1839                    (want - have).abs() <= step * 0.5 + slack,
1840                    "{name}: {} value {i} came back {have} for {want}, step {step}",
1841                    column.name
1842                );
1843            }
1844        }
1845    }
1846}
1847
1848#[cfg(all(test, feature = "point_cloud_decode", feature = "encoder"))]
1849mod pair_tests {
1850    use super::*;
1851    use crate::encoder_buffer::EncoderBuffer;
1852    use crate::encoder_options::EncoderOptions;
1853    use crate::point_cloud_encoder::PointCloudEncoder;
1854
1855    struct Xorshift(u64);
1856
1857    impl Xorshift {
1858        fn next(&mut self) -> u64 {
1859            self.0 ^= self.0 << 13;
1860            self.0 ^= self.0 >> 7;
1861            self.0 ^= self.0 << 17;
1862            self.0
1863        }
1864
1865        fn unit(&mut self) -> f32 {
1866            (self.next() >> 40) as f32 / (1u64 << 24) as f32
1867        }
1868    }
1869
1870    fn float_attribute(
1871        kind: GeometryAttributeType,
1872        components: u8,
1873        values: &[f32],
1874    ) -> PointAttribute {
1875        let points = values.len() / components as usize;
1876        let mut attribute = PointAttribute::new();
1877        attribute.init(kind, components, DataType::Float32, false, points);
1878        for (i, value) in values.iter().enumerate() {
1879            attribute.buffer_mut().write(i * 4, &value.to_le_bytes());
1880        }
1881        attribute
1882    }
1883
1884    fn byte_attribute(
1885        kind: GeometryAttributeType,
1886        components: u8,
1887        values: &[u8],
1888    ) -> PointAttribute {
1889        let points = values.len() / components as usize;
1890        let mut attribute = PointAttribute::new();
1891        attribute.init(kind, components, DataType::Uint8, false, points);
1892        for (i, value) in values.iter().enumerate() {
1893            attribute.buffer_mut().write(i, &[*value]);
1894        }
1895        attribute
1896    }
1897
1898    /// Every kind of attribute stream a sequential cloud carries -- a position
1899    /// the coder writes tagged, normals, quantized floats, bytes, raw floats,
1900    /// attributes that never vary -- with noise in the rest, so the stream is
1901    /// as large as the values.
1902    fn stream(points: usize, seed: u64) -> Vec<u8> {
1903        let mut rng = Xorshift(seed);
1904        let mut cloud = PointCloud::new();
1905        cloud.set_num_points(points);
1906        let mut options = EncoderOptions::new();
1907        options.set_encoding_method(0);
1908        let mut next_id = 0;
1909        let mut add = |cloud: &mut PointCloud, attribute: PointAttribute, bits: i32| {
1910            cloud.add_attribute(attribute);
1911            if bits > 0 {
1912                options.set_attribute_int(next_id, "quantization_bits", bits);
1913            }
1914            next_id += 1;
1915        };
1916        let positions: Vec<f32> = (0..points * 3).map(|_| rng.unit() * 100.0).collect();
1917        add(
1918            &mut cloud,
1919            float_attribute(GeometryAttributeType::Position, 3, &positions),
1920            16,
1921        );
1922        let normals: Vec<f32> = (0..points * 3).map(|_| rng.unit() * 2.0 - 1.0).collect();
1923        add(
1924            &mut cloud,
1925            float_attribute(GeometryAttributeType::Normal, 3, &normals),
1926            8,
1927        );
1928        for _ in 0..40 {
1929            let values: Vec<f32> = (0..points).map(|_| rng.unit()).collect();
1930            add(
1931                &mut cloud,
1932                float_attribute(GeometryAttributeType::Generic, 1, &values),
1933                8,
1934            );
1935        }
1936        let colours: Vec<u8> = (0..points * 3).map(|_| (rng.next() >> 56) as u8).collect();
1937        add(
1938            &mut cloud,
1939            byte_attribute(GeometryAttributeType::Color, 3, &colours),
1940            0,
1941        );
1942        // No quantization on a float: the coder copies it as raw bytes.
1943        let raw: Vec<f32> = (0..points).map(|_| rng.unit()).collect();
1944        add(
1945            &mut cloud,
1946            float_attribute(GeometryAttributeType::Generic, 1, &raw),
1947            0,
1948        );
1949        for _ in 0..3 {
1950            let zeros = vec![0.0f32; points];
1951            add(
1952                &mut cloud,
1953                float_attribute(GeometryAttributeType::Generic, 1, &zeros),
1954                8,
1955            );
1956        }
1957        let tags: Vec<u8> = (0..points).map(|_| (rng.next() >> 56) as u8).collect();
1958        add(
1959            &mut cloud,
1960            byte_attribute(GeometryAttributeType::Generic, 1, &tags),
1961            0,
1962        );
1963        let mut encoder = PointCloudEncoder::new();
1964        encoder.set_point_cloud(cloud);
1965        let mut buffer = EncoderBuffer::new();
1966        encoder.encode(&options, &mut buffer).expect("encodes");
1967        buffer.data().to_vec()
1968    }
1969
1970    /// What a decode makes of `bytes` -- every attribute's bytes or the error
1971    /// -- and where it left the allocation budget, with the pairs on or off.
1972    fn decode(
1973        bytes: &[u8],
1974        pairs: bool,
1975    ) -> (Result<Vec<Vec<u8>>, String>, crate::decoder_buffer::Budget) {
1976        PAIRS_OFF.with(|off| off.set(!pairs));
1977        let mut buffer = DecoderBuffer::new(bytes);
1978        let mut cloud = PointCloud::new();
1979        // On one thread, which decodes in order and is where the pairs run.
1980        let mut decoder = PointCloudDecoder::new();
1981        decoder.set_threads(1);
1982        let decoded = decoder
1983            .decode(&mut buffer, &mut cloud)
1984            .map_err(|e| e.to_string())
1985            .map(|()| {
1986                (0..cloud.num_attributes())
1987                    .map(|id| cloud.attribute(id).buffer().data().to_vec())
1988                    .collect()
1989            });
1990        PAIRS_OFF.with(|off| off.set(false));
1991        (decoded, buffer.budget())
1992    }
1993
1994    fn paired_by<T>(f: impl FnOnce() -> T) -> (T, usize) {
1995        let before = PAIRED.with(|paired| paired.get());
1996        let out = f();
1997        (out, PAIRED.with(|paired| paired.get()) - before)
1998    }
1999
2000    /// The pairs decode the cloud the one-after-the-other decode does, value
2001    /// for value, and leave the budget where it leaves it. All but the tagged
2002    /// position, the raw float and an odd one out are paired, the normals'
2003    /// two octahedral coordinates included.
2004    #[test]
2005    fn paired_streams_decode_the_same_cloud_value_for_value() {
2006        let bytes = stream(40_000, 1);
2007        let ((alone, budget_alone), paired) = paired_by(|| decode(&bytes, false));
2008        assert_eq!(paired, 0, "the reference paired");
2009        let ((together, budget_together), paired) = paired_by(|| decode(&bytes, true));
2010        assert!(paired >= 44, "{paired} attributes paired");
2011        assert_eq!(together.expect("decodes"), alone.expect("decodes"));
2012        assert_eq!(budget_together, budget_alone, "the budget");
2013    }
2014
2015    #[test]
2016    fn a_damaged_stream_is_read_paired_as_alone() {
2017        let bytes = stream(20_000, 3);
2018        let mut rng = Xorshift(77);
2019        for trial in 0..40 {
2020            let mut damaged = bytes.clone();
2021            // Bytes anywhere, and bytes in the header and the attribute
2022            // descriptions, where the lengths come from.
2023            let at = if trial % 2 == 0 {
2024                rng.next() as usize % damaged.len()
2025            } else {
2026                rng.next() as usize % 400
2027            };
2028            damaged[at] ^= 1 << (rng.next() % 8);
2029            assert_eq!(
2030                decode(&damaged, true),
2031                decode(&damaged, false),
2032                "trial {trial}: byte {at} damaged"
2033            );
2034        }
2035        // And cut short.
2036        for cut in [bytes.len() / 2, bytes.len() - 1, bytes.len() - 40, 5000] {
2037            assert_eq!(
2038                decode(&bytes[..cut], true),
2039                decode(&bytes[..cut], false),
2040                "cut at {cut}"
2041            );
2042        }
2043    }
2044
2045    /// Every synthetic shape, in its own order and shuffled, from no points to
2046    /// past where the decode's run forms pay: the same values and budget
2047    /// decoded with the pairs and without, and those values the source's -- to
2048    /// the bit where nothing was quantized, within half a step where it was.
2049    /// The large clouds must pair somewhere, and `skewed`'s bimodal attribute
2050    /// is decoded through the rANS buckets.
2051    #[test]
2052    fn synthetic_clouds_come_back_the_same_by_every_path() {
2053        use crate::synthetic_cloud::{self, Cloud};
2054        let mut clouds: Vec<Cloud> = [0, 1, 2, 3, 97]
2055            .into_iter()
2056            .flat_map(|points| synthetic_cloud::all(points, 1))
2057            .collect();
2058        let big = synthetic_cloud::all(400_000, 2);
2059        clouds.extend(big.iter().map(|cloud| cloud.shuffled(3)));
2060        clouds.extend(big);
2061        let paired: usize = std::thread::scope(|scope| {
2062            let runs: Vec<_> = clouds
2063                .iter()
2064                .map(|cloud| scope.spawn(|| paired_by(|| every_path_agrees(cloud)).1))
2065                .collect();
2066            runs.into_iter()
2067                .map(|run| run.join().expect("a cloud's check panicked"))
2068                .sum()
2069        });
2070        assert!(paired > 0, "no synthetic cloud paired a single stream");
2071    }
2072
2073    fn every_path_agrees(cloud: &crate::synthetic_cloud::Cloud) {
2074        let name = format!("{} of {} points", cloud.name, cloud.points);
2075        let mut options = EncoderOptions::new();
2076        options.set_encoding_method(0);
2077        for (id, column) in cloud.columns.iter().enumerate() {
2078            if column.quantization_bits > 0 {
2079                options.set_attribute_int(id as i32, "quantization_bits", column.quantization_bits);
2080            }
2081        }
2082        let mut encoder = PointCloudEncoder::new();
2083        encoder.set_point_cloud(cloud.to_point_cloud());
2084        let mut buffer = EncoderBuffer::new();
2085        // An empty attribute has no range to quantize over, and the encoder
2086        // says so rather than read a value that is not there.
2087        match encoder.encode(&options, &mut buffer) {
2088            Err(e) if cloud.points == 0 && e.to_string().contains("empty attribute") => return,
2089            result => result.unwrap_or_else(|e| panic!("{name}: {e}")),
2090        }
2091        let bytes = buffer.data().to_vec();
2092        let (alone, budget_alone) = decode(&bytes, false);
2093        let decoded = alone.unwrap_or_else(|e| panic!("{name}: {e}"));
2094        let (together, budget_together) = decode(&bytes, true);
2095        assert!(
2096            together.as_ref() == Ok(&decoded),
2097            "{name}: the paired decode differs"
2098        );
2099        assert_eq!(budget_together, budget_alone, "{name}: the budget");
2100        for (column, got) in cloud.columns.iter().zip(&decoded) {
2101            let source = column.values.bytes();
2102            let got = &got[..source.len()];
2103            if column.quantization_bits == 0 {
2104                assert!(got == source, "{name}: {} changed", column.name);
2105                continue;
2106            }
2107            let values = |bytes: &[u8]| -> Vec<f32> {
2108                bytes
2109                    .as_chunks::<4>()
2110                    .0
2111                    .iter()
2112                    .map(|&b| f32::from_le_bytes(b))
2113                    .collect()
2114            };
2115            let (source, got) = (values(&source), values(got));
2116            if column.kind == GeometryAttributeType::Normal {
2117                // Octahedral coordinates on a grid of `bits` a side: a unit
2118                // vector comes back within a few cells. A zero one, which the
2119                // octahedron has no point for, comes back as some direction.
2120                let cell = 4.0 / (1u32 << column.quantization_bits) as f32;
2121                let pairs = source.as_chunks::<3>().0.iter().zip(got.as_chunks::<3>().0);
2122                for (i, (want, have)) in pairs.enumerate() {
2123                    if want.iter().all(|&v| v == 0.0) {
2124                        continue;
2125                    }
2126                    let error = want
2127                        .iter()
2128                        .zip(have)
2129                        .map(|(w, h)| (w - h).abs())
2130                        .fold(0.0, f32::max);
2131                    assert!(
2132                        error <= 2.0 * cell,
2133                        "{name}: normal {i} came back {have:?} for {want:?}"
2134                    );
2135                }
2136                continue;
2137            }
2138            // The quantizer's grid spans the widest component's range.
2139            let range = (0..column.components)
2140                .map(|c| {
2141                    let component = source.iter().skip(c).step_by(column.components);
2142                    let (lo, hi) =
2143                        component.fold((f32::MAX, f32::MIN), |(lo, hi), &v| (lo.min(v), hi.max(v)));
2144                    hi - lo
2145                })
2146                .fold(0.0f32, f32::max);
2147            let step = range / ((1u32 << column.quantization_bits) - 1) as f32;
2148            for (i, (&want, &have)) in source.iter().zip(&got).enumerate() {
2149                let slack = (want.abs() + range) * f32::EPSILON * 4.0;
2150                assert!(
2151                    (want - have).abs() <= step * 0.5 + slack,
2152                    "{name}: {} value {i} came back {have} for {want}, step {step}",
2153                    column.name
2154                );
2155            }
2156        }
2157    }
2158}