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::point_cloud::PointCloud;
12#[cfg(feature = "point_cloud_decode")]
13use crate::prediction_scheme::EntryToPointIdMap;
14#[cfg(feature = "point_cloud_decode")]
15use crate::sequential_integer_attribute_decoder::{
16    PortableExtent, SequentialIntegerAttributeDecoder,
17};
18#[cfg(feature = "point_cloud_decode")]
19use crate::status::{DracoError, Status};
20
21#[cfg(feature = "point_cloud_decode")]
22use crate::attribute_octahedron_transform::AttributeOctahedronTransform;
23#[cfg(feature = "point_cloud_decode")]
24use crate::attribute_quantization_transform::AttributeQuantizationTransform;
25#[cfg(feature = "point_cloud_decode")]
26use crate::attribute_transform::AttributeTransform;
27#[cfg(feature = "point_cloud_decode")]
28use crate::sequential_generic_attribute_decoder::SequentialGenericAttributeDecoder;
29#[cfg(feature = "point_cloud_decode")]
30use crate::sequential_normal_attribute_decoder::SequentialNormalAttributeDecoder;
31#[cfg(feature = "point_cloud_decode")]
32use crate::sequential_quantization_attribute_decoder::SequentialQuantizationAttributeDecoder;
33#[cfg(feature = "point_cloud_decode")]
34use crate::version::{version_at_least, VERSION_FLAGS_INTRODUCED};
35
36/// Whether a prediction transform byte follows this prediction method byte.
37///
38/// Upstream writes the transform only when the method is not `PREDICTION_NONE`,
39/// which is `-2` and reaches the stream as `0xFE`. `0xFF` is `-1`, which this
40/// crate once wrote for the same meaning, so both are read as "nothing follows"
41/// -- the same pair `SequentialIntegerAttributeDecoder` accepts.
42///
43/// The pre-1.2 shims below need this because they walk the prediction header by
44/// hand to reach the quantization parameters behind it. Testing `0xFF` alone
45/// made them step one byte into a `PREDICTION_NONE` stream and read the
46/// parameters shifted: the range came out zero and every position dequantized to
47/// the origin, with the point and face counts still right and the decode still
48/// reporting success. Draco writes `PREDICTION_NONE` at compression level 0.
49/// Gated on the feature its callers live behind, and only that one: both of
50/// them are now the pre-2.0 shims inside the shared normal and quantization
51/// decoders, so a `point_cloud_decode` build without legacy support compiles
52/// neither and would carry this as dead code.
53#[cfg(feature = "legacy_bitstream_decode")]
54pub(crate) fn carries_transform_byte(method_byte: u8) -> bool {
55    method_byte != 0xFF && method_byte != 0xFE
56}
57
58/// Decoder for Draco point cloud bitstreams.
59///
60/// `PointCloudDecoder` reads a point-cloud `.drc` bitstream and reconstructs a
61/// [`PointCloud`] with its attributes and metadata. Both
62/// KD-tree and sequential attribute encodings are supported (the actual decode
63/// requires the `point_cloud_decode` feature).
64///
65/// A round trip is shown on the `PointCloudEncoder` type docs.
66pub struct PointCloudDecoder {
67    geometry_type: EncodedGeometryType,
68    #[cfg(feature = "point_cloud_decode")]
69    method: u8,
70    #[cfg(feature = "point_cloud_decode")]
71    flags: u16,
72    /// Ungated, unlike the fields above: `bitstream_version` is read by the
73    /// attribute decoders on both the mesh and the point-cloud path, and the
74    /// mesh path exists without `point_cloud_decode`.
75    version_major: u8,
76    version_minor: u8,
77}
78
79impl Default for PointCloudDecoder {
80    fn default() -> Self {
81        Self::new()
82    }
83}
84
85#[cfg(feature = "point_cloud_decode")]
86fn validate_num_attributes_in_decoder(
87    num_attributes_in_decoder: usize,
88    remaining_bytes: usize,
89) -> Result<(), DracoError> {
90    // Each attribute must have at least type, data type, component count,
91    // normalized flag, unique id, and a decoder type byte. Reject impossible
92    // counts before reserving vectors from untrusted input.
93    const MIN_ATTRIBUTE_BYTES: usize = 6;
94    if num_attributes_in_decoder == 0
95        || num_attributes_in_decoder > remaining_bytes / MIN_ATTRIBUTE_BYTES
96    {
97        return Err(DracoError::general(
98            "Invalid number of attributes".to_string(),
99        ));
100    }
101    Ok(())
102}
103
104#[cfg(feature = "point_cloud_decode")]
105fn validate_num_components(num_components: u8) -> Result<(), DracoError> {
106    if num_components == 0 {
107        return Err(DracoError::general(
108            "Invalid attribute component count".to_string(),
109        ));
110    }
111    Ok(())
112}
113
114impl PointCloudDecoder {
115    /// Creates a point cloud decoder with default state.
116    pub fn new() -> Self {
117        Self {
118            geometry_type: EncodedGeometryType::PointCloud,
119            #[cfg(feature = "point_cloud_decode")]
120            method: 0,
121            #[cfg(feature = "point_cloud_decode")]
122            flags: 0,
123            version_major: 0,
124            version_minor: 0,
125        }
126    }
127
128    /// The packed bitstream version (`0xMMmm`), `0` before a header was read.
129    ///
130    /// The attribute decoders read it when they bind prediction parents, which
131    /// is upstream's `decoder_->bitstream_version()` inside
132    /// `InitPredictionScheme`.
133    pub(crate) fn bitstream_version(&self) -> u16 {
134        crate::version::bitstream_version(self.version_major, self.version_minor)
135    }
136
137    /// Carries the version to a decoder that did not read the header itself.
138    ///
139    /// The mesh path parses its own header and then hands attributes to these
140    /// decoders through a `PointCloudDecoder` it constructs on the spot, so
141    /// without this that decoder reports version zero -- and every parent
142    /// binding on the fallback path would read as pre-2.0.
143    pub(crate) fn set_bitstream_version(&mut self, major: u8, minor: u8) {
144        self.version_major = major;
145        self.version_minor = minor;
146    }
147
148    #[cfg(feature = "point_cloud_decode")]
149    /// Decodes a Draco point cloud from `in_buffer` into `out_pc`.
150    ///
151    /// `out_pc` need not be empty: whatever it held is replaced, the same way
152    /// [`MeshDecoder::decode`](crate::mesh_decoder::MeshDecoder::decode)
153    /// replaces its mesh. `decode_after_header` does not clear, because its
154    /// caller has already done so and has decoded metadata since.
155    ///
156    /// # Errors
157    ///
158    /// Returns an error if the header is invalid, the bitstream version is
159    /// unsupported, or the encoded attributes are malformed.
160    pub fn decode(&mut self, in_buffer: &mut DecoderBuffer, out_pc: &mut PointCloud) -> Status {
161        out_pc.clear();
162
163        // 1. Decode Header
164        self.decode_header(in_buffer)?;
165
166        if version_at_least(
167            self.version_major,
168            self.version_minor,
169            VERSION_FLAGS_INTRODUCED,
170        ) && (self.flags & crate::metadata::METADATA_FLAG_MASK) != 0
171        {
172            let metadata = crate::metadata::GeometryMetadata::decode(in_buffer)
173                .map_err(|_| DracoError::general("Failed to decode metadata".to_string()))?;
174            out_pc.set_metadata(Some(metadata));
175        }
176
177        // 2. Decode Geometry Data
178        self.decode_geometry_data(in_buffer, out_pc)
179    }
180
181    /// Decode point cloud data when header + metadata have already been parsed.
182    /// Used by MeshDecoder to delegate point cloud streams.
183    #[cfg(feature = "point_cloud_decode")]
184    pub fn decode_after_header(
185        &mut self,
186        version_major: u8,
187        version_minor: u8,
188        method: u8,
189        buffer: &mut DecoderBuffer,
190        out_pc: &mut PointCloud,
191    ) -> Status {
192        self.version_major = version_major;
193        self.version_minor = version_minor;
194        self.method = method;
195        self.flags = 0;
196        self.geometry_type = EncodedGeometryType::PointCloud;
197        self.decode_geometry_data(buffer, out_pc)
198    }
199
200    #[cfg(feature = "point_cloud_decode")]
201    fn decode_header(&mut self, buffer: &mut DecoderBuffer) -> Status {
202        let mut magic = [0u8; 5];
203        buffer.decode_bytes(&mut magic)?;
204        if &magic != b"DRACO" {
205            return Err(DracoError::general("Invalid magic".to_string()));
206        }
207
208        self.version_major = buffer.decode_u8()?;
209        self.version_minor = buffer.decode_u8()?;
210        buffer.set_version(self.version_major, self.version_minor);
211
212        let g_type = buffer.decode_u8()?;
213        self.geometry_type = match g_type {
214            0 => EncodedGeometryType::PointCloud,
215            1 => EncodedGeometryType::TriangularMesh,
216            _ => return Err(DracoError::general("Invalid geometry type".to_string())),
217        };
218        if self.geometry_type != EncodedGeometryType::PointCloud {
219            return Err(DracoError::general(
220                "PointCloudDecoder cannot decode mesh bitstreams".to_string(),
221            ));
222        }
223
224        self.method = buffer.decode_u8()?;
225
226        // Flags field is always present in the binary header (C++ reads unconditionally).
227        self.flags = buffer
228            .decode_u16()
229            .map_err(|_| DracoError::general("Failed to decode flags".to_string()))?;
230
231        Ok(())
232    }
233
234    #[cfg(feature = "point_cloud_decode")]
235    fn decode_geometry_data(&mut self, buffer: &mut DecoderBuffer, pc: &mut PointCloud) -> Status {
236        let bitstream_version: u16 =
237            crate::version::bitstream_version(self.version_major, self.version_minor);
238        // Note: Draco point cloud bitstreams encode the number of points as a
239        // fixed-width int32 for both sequential (method=0) and KD-tree
240        // (method=1) encodings (see C++ PointCloudSequentialDecoder and
241        // PointCloudKdTreeDecoder). It is NOT varint encoded, even for v2.x.
242        // Read as the `int32_t` upstream reads, and refused when negative for
243        // the same reason `PointCloudSequentialDecoder` and
244        // `PointCloudKdTreeDecoder` refuse it: no encoder writes a count with
245        // the sign bit set, and C++ Draco stops on one before it allocates
246        // anything. Taking the same bytes unsigned is how a header claiming
247        // 2,147,483,652 points reached the KD-tree walk, which then expanded a
248        // single run-length node into 2.4 GB -- an OOM the `decode_drc` soak
249        // found, on a file upstream rejects in a fifth of a second having
250        // touched no memory at all.
251        //
252        // Past this the count is used but not guarded, which is also upstream's
253        // shape: what bounds the work is the allocation budget applied where
254        // the buffers are sized -- see `decode_budget`.
255        let declared_points = buffer.decode_u32()? as i32;
256        if declared_points < 0 {
257            return Err(DracoError::general(format!(
258                "Point cloud declares {declared_points} points"
259            )));
260        }
261        let num_points: usize = declared_points as usize;
262        buffer.check_points(num_points)?;
263        pc.set_num_points(num_points);
264
265        let num_attributes_decoders = buffer.decode_u8()? as usize;
266
267        if self.method == 1 {
268            // KD-tree encoding.
269            for _ in 0..num_attributes_decoders {
270                let mut att_decoder = KdTreeAttributesDecoder::new(0);
271                att_decoder
272                    .decode_attributes_decoder_data(pc, buffer)
273                    .map_err(|err| err.context("Failed to decode attribute metadata"))?;
274                att_decoder
275                    .decode_attributes(pc, buffer)
276                    .map_err(|err| err.context("Failed to decode attributes"))?;
277            }
278        } else {
279            // Sequential encoding.
280            struct PendingQuant {
281                att_id: i32,
282                portable: PointAttribute,
283                transform: AttributeQuantizationTransform,
284            }
285
286            struct PendingNormal {
287                att_id: i32,
288                portable: PointAttribute,
289                quantization_bits: u8,
290            }
291
292            struct AttributeSpec {
293                att_type: GeometryAttributeType,
294                data_type: DataType,
295                num_components: u8,
296                normalized: bool,
297                unique_id: u32,
298            }
299
300            for _ in 0..num_attributes_decoders {
301                let num_attributes_in_decoder: usize = if bitstream_version < 0x0200 {
302                    buffer.decode_u32()? as usize
303                } else {
304                    buffer.decode_varint()? as usize
305                };
306                if num_attributes_in_decoder == 0 {
307                    return Err(DracoError::general(
308                        "Invalid number of attributes".to_string(),
309                    ));
310                }
311                validate_num_attributes_in_decoder(
312                    num_attributes_in_decoder,
313                    buffer.remaining_size(),
314                )?;
315
316                let mut attribute_specs: Vec<AttributeSpec> =
317                    Vec::with_capacity(num_attributes_in_decoder);
318                let mut att_ids: Vec<i32> = Vec::with_capacity(num_attributes_in_decoder);
319                let mut decoder_types: Vec<u8> = Vec::with_capacity(num_attributes_in_decoder);
320                let mut pending_quant: Vec<PendingQuant> = Vec::new();
321                let mut pending_normals: Vec<PendingNormal> = Vec::new();
322
323                for _ in 0..num_attributes_in_decoder {
324                    let att_type_val = buffer.decode_u8()?;
325                    let att_type = GeometryAttributeType::try_from(att_type_val)?;
326
327                    let data_type_val = buffer.decode_u8()?;
328                    let data_type = DataType::try_from(data_type_val)?;
329
330                    let num_components = buffer.decode_u8()?;
331                    validate_num_components(num_components)?;
332                    let normalized = buffer.decode_u8()? != 0;
333                    let unique_id: u32 = if bitstream_version < 0x0103 {
334                        buffer.decode_u16()? as u32
335                    } else {
336                        buffer.decode_varint()? as u32
337                    };
338
339                    attribute_specs.push(AttributeSpec {
340                        att_type,
341                        data_type,
342                        num_components,
343                        normalized,
344                        unique_id,
345                    });
346                }
347
348                for _ in 0..num_attributes_in_decoder {
349                    decoder_types.push(buffer.decode_u8()?);
350                }
351
352                for (local_i, spec) in attribute_specs.iter().enumerate() {
353                    if decoder_types[local_i] == 0 {
354                        let entry_size =
355                            spec.num_components as usize * spec.data_type.byte_length();
356                        let bytes_needed = entry_size.checked_mul(num_points).ok_or_else(|| {
357                            DracoError::general(
358                                "Raw point cloud attribute byte count overflow".to_string(),
359                            )
360                        })?;
361                        if buffer.remaining_size() < bytes_needed {
362                            return Err(DracoError::general(
363                                "Not enough data for raw point cloud attribute values".to_string(),
364                            ));
365                        }
366                    }
367
368                    buffer.charge_decoded_bytes(
369                        (spec.num_components as usize)
370                            .saturating_mul(spec.data_type.byte_length())
371                            .saturating_mul(num_points),
372                    )?;
373                    let mut att = PointAttribute::new();
374                    // Nothing is charged against the *budget* for this
375                    // attribute, because nothing is taken for it: the buffer is
376                    // left unreserved and sized by whichever decoder writes the
377                    // values, once they exist. A charge there would be for an
378                    // allocation that no longer happens, and it is not free --
379                    // the budget is a backstop against unbacked reservations,
380                    // and billing it for backed ones is what made it refuse
381                    // files this crate writes. The caller's ceiling above is
382                    // the other question and is charged: it bounds what the
383                    // decode may produce at all, backed or not.
384                    att.init_deferred(
385                        spec.att_type,
386                        spec.num_components,
387                        spec.data_type,
388                        spec.normalized,
389                        num_points,
390                    )?;
391                    att.set_unique_id(spec.unique_id);
392                    let att_id = pc.add_attribute_preserve_unique_id(att);
393                    att_ids.push(att_id);
394                }
395
396                // The identity, and not written out. Entry `i` is point `i`
397                // here, so materializing it bought nothing and cost four bytes
398                // per point of a count the header supplies -- 134 MB from a
399                // 9 KB stream on one fuzz artifact, and gigabytes on a bigger
400                // claim. See `EntryToPointIdMap::Identity`.
401                let point_ids = if decoder_types.iter().any(|&decoder_type| decoder_type != 0) {
402                    Some(EntryToPointIdMap::identity(num_points))
403                } else {
404                    None
405                };
406
407                for (local_i, &att_id) in att_ids.iter().enumerate() {
408                    let decoder_type = decoder_types[local_i];
409                    match decoder_type {
410                        1 => {
411                            let point_ids = point_ids.ok_or_else(|| {
412                                DracoError::general(
413                                    "Point ids missing for integer attribute decoder".to_string(),
414                                )
415                            })?;
416                            let mut att_decoder = SequentialIntegerAttributeDecoder::new();
417                            att_decoder.init(self, att_id);
418                            att_decoder.decode_values(
419                                pc, point_ids, buffer, None, None, None, None, None, None,
420                            )?;
421                        }
422                        2 => {
423                            let mut att_decoder = SequentialQuantizationAttributeDecoder::new();
424                            att_decoder.init(self, pc, att_id)?;
425                            let portable = att_decoder.decode_values(
426                                pc,
427                                point_ids.ok_or_else(|| {
428                                    DracoError::general(
429                                        "Point ids missing for quantized attribute decoder"
430                                            .to_string(),
431                                    )
432                                })?,
433                                buffer,
434                                bitstream_version,
435                                PortableExtent::Declared(num_points),
436                                None,
437                                None,
438                                None,
439                                None,
440                            )?;
441                            pending_quant.push(PendingQuant {
442                                att_id,
443                                portable,
444                                transform: att_decoder.into_transform(),
445                            });
446                        }
447                        3 => {
448                            let mut att_decoder = SequentialNormalAttributeDecoder::new();
449                            att_decoder.init(self, pc, att_id)?;
450                            let portable = att_decoder.decode_values(
451                                pc,
452                                point_ids.ok_or_else(|| {
453                                    DracoError::general(
454                                        "Point ids missing for normal attribute decoder"
455                                            .to_string(),
456                                    )
457                                })?,
458                                buffer,
459                                bitstream_version,
460                                PortableExtent::Declared(num_points),
461                                None,
462                                None,
463                                None,
464                                None,
465                            )?;
466                            pending_normals.push(PendingNormal {
467                                att_id,
468                                portable,
469                                quantization_bits: att_decoder.quantization_bits(),
470                            });
471                        }
472                        0 => {
473                            // The identity map costs nothing to build and is
474                            // all this decoder reads off it -- the values are
475                            // copied verbatim, in order -- so the arm does not
476                            // need the shared `point_ids`, which is `None` when
477                            // every attribute is generic.
478                            let mut att_decoder = SequentialGenericAttributeDecoder::new();
479                            att_decoder.init(self, att_id);
480                            att_decoder.decode_values(
481                                pc,
482                                EntryToPointIdMap::identity(num_points),
483                                buffer,
484                            )?;
485                        }
486                        _ => {
487                            return Err(DracoError::general(format!(
488                                "Unsupported sequential decoder type: {}",
489                                decoder_type
490                            )));
491                        }
492                    }
493                }
494
495                for (local_i, &att_id) in att_ids.iter().enumerate() {
496                    match decoder_types[local_i] {
497                        2 if bitstream_version >= 0x0200 => {
498                            let idx = pending_quant
499                                .iter()
500                                .position(|p| p.att_id == att_id)
501                                .ok_or_else(|| {
502                                    DracoError::general(
503                                        "Missing pending quantized attribute transform".to_string(),
504                                    )
505                                })?;
506                            let original = pc.try_attribute(att_id)?;
507                            pending_quant[idx]
508                                .transform
509                                .decode_parameters(original, buffer)
510                                .map_err(|e| {
511                                    DracoError::general(format!(
512                                        "Failed to decode quantization parameters: {e}"
513                                    ))
514                                })?;
515                        }
516                        3 if bitstream_version >= 0x0200 => {
517                            let idx = pending_normals
518                                .iter()
519                                .position(|p| p.att_id == att_id)
520                                .ok_or_else(|| {
521                                    DracoError::general(
522                                        "Missing pending normal attribute transform".to_string(),
523                                    )
524                                })?;
525                            let quantization_bits = buffer.decode_u8()?;
526                            if !AttributeOctahedronTransform::is_valid_quantization_bits(
527                                quantization_bits as i32,
528                            ) {
529                                return Err(DracoError::general(
530                                    "Invalid normal quantization bits".to_string(),
531                                ));
532                            }
533                            pending_normals[idx].quantization_bits = quantization_bits;
534                        }
535                        _ => {}
536                    }
537                }
538
539                for q in pending_quant {
540                    let dst = pc.try_attribute_mut(q.att_id)?;
541                    q.transform
542                        .inverse_transform_attribute(&q.portable, dst)
543                        .map_err(|e| {
544                            DracoError::general(format!("Failed to dequantize attribute: {e}"))
545                        })?;
546                }
547                for n in pending_normals {
548                    let mut oct = AttributeOctahedronTransform::new(-1);
549                    oct.set_parameters(n.quantization_bits as i32)?;
550                    let dst = pc.try_attribute_mut(n.att_id)?;
551                    oct.inverse_transform_attribute_with_legacy_octahedron(
552                        &n.portable,
553                        dst,
554                        bitstream_version < 0x0200,
555                    )
556                    .map_err(|e| DracoError::general(format!("Failed to decode normals: {e}")))?;
557                }
558            }
559        }
560
561        Ok(())
562    }
563
564    /// Returns the encoded geometry type handled by this decoder.
565    pub fn get_geometry_type(&self) -> EncodedGeometryType {
566        self.geometry_type
567    }
568}