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#[cfg(feature = "legacy_bitstream_decode")]
58pub(crate) fn carries_transform_byte(method_byte: u8) -> bool {
59 method_byte != 0xFF && method_byte != 0xFE
60}
61
62pub 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 version_major: u8,
80 version_minor: u8,
81 #[cfg(feature = "point_cloud_decode")]
82 threads: i32,
83}
84
85#[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#[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#[cfg(all(test, feature = "point_cloud_decode"))]
110thread_local! {
111 static ENGAGED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
112 static PAIRED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
114 static IN_ORDER: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
117 static PAIRS_OFF: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
120}
121
122#[cfg(feature = "point_cloud_decode")]
125struct PendingQuant {
126 att_id: i32,
127 portable: PointAttribute,
128 transform: AttributeQuantizationTransform,
129}
130
131#[cfg(feature = "point_cloud_decode")]
134struct PendingNormal {
135 att_id: i32,
136 portable: PointAttribute,
137 quantization_bits: u8,
138}
139
140#[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#[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 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 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 #[cfg(feature = "point_cloud_decode")]
280 pub fn set_threads(&mut self, threads: i32) {
281 self.threads = threads;
282 }
283
284 pub(crate) fn bitstream_version(&self) -> u16 {
290 crate::version::bitstream_version(self.version_major, self.version_minor)
291 }
292
293 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 pub fn decode(&mut self, in_buffer: &mut DecoderBuffer, out_pc: &mut PointCloud) -> Status {
317 out_pc.clear();
318
319 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 self.decode_geometry_data(in_buffer, out_pc)
335 }
336
337 #[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 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 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 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 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 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 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 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 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 pub fn get_geometry_type(&self) -> EncodedGeometryType {
698 self.geometry_type
699 }
700}
701
702#[cfg(feature = "point_cloud_decode")]
703impl PointCloudDecoder {
704 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 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 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 #[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 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 #[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 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 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 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 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 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 #[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#[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#[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 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 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 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 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 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 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 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 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 #[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 #[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 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 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 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 #[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 #[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 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 #[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 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 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 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 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 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 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 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 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 #[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 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 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 #[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 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 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 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}