Skip to main content

draco_oxide/encode/attribute/
mod.rs

1pub(crate) mod attribute_encoder;
2pub(crate) mod portabilization;
3pub(crate) mod prediction_transform;
4
5use crate::encode::attribute::portabilization::PortabilizationType;
6pub use crate::encode::attribute::portabilization::Quantization;
7pub use crate::encode::attribute::prediction_transform::PredictionTransformType;
8#[cfg(feature = "evaluation")]
9use crate::eval;
10
11use std::collections::HashMap;
12
13use draco_oxide_core::attribute::{Attribute, AttributeType};
14use draco_oxide_core::bit_coder::ByteWriter;
15use draco_oxide_core::codec::attribute::prediction_scheme::PredictionSchemeType;
16use draco_oxide_core::codec::connectivity::edgebreaker::TraversalType;
17use draco_oxide_core::mesh::ds::AttributeDS;
18use draco_oxide_core::types::{ConfigType, CornerIdx};
19
20pub fn encode_attributes<W>(
21    adss: Vec<AttributeDS>,
22    // Corners of the edgebreaker traversal, produced by connectivity encoding and used to seed
23    // each attribute's sequencing.
24    corners_of_edgebreaker: Vec<CornerIdx>,
25    writer: &mut W,
26    cfg: &super::Config,
27) -> Result<(), Err>
28where
29    W: ByteWriter,
30{
31    #[cfg(feature = "evaluation")]
32    eval::scope_begin("attributes", writer);
33
34    // Write the number of attribute encoders/decoders (In draco-oxide, this is the same as the number of attributes as
35    // each attribute has its own encoder/decoder)
36    writer.write_u8(adss.len() as u8);
37    #[cfg(feature = "evaluation")]
38    eval::write_json_pair("attributes count", adss.len().into(), writer);
39
40    for (i, att) in adss.iter().enumerate() {
41        if cfg.connectivity.encoder_method()
42            == draco_oxide_core::codec::header::EncoderMethod::Edgebreaker
43        {
44            // encode decoder id
45            writer.write_u8((i as u8).wrapping_sub(1));
46            // encode attribute type
47            att.att_data().get_domain().write_to(writer);
48            // write traversal method for attribute encoding/decoding sequencer. We currently only support depth-first traversal.
49            TraversalType::DepthFirst.write_to(writer);
50        }
51    }
52
53    #[cfg(feature = "evaluation")]
54    eval::array_scope_begin("attributes", writer);
55
56    let mut port_atts: Vec<Attribute> = Vec::new();
57    for att in &adss {
58        // Write 1 to indicate that the encoder is for one attribute.
59        writer.write_u8(1);
60
61        att.att_data().get_attribute_type().write_to(writer);
62        att.att_data().get_component_type().write_to(writer);
63        writer.write_u8(att.att_data().get_num_components() as u8);
64        writer.write_u8(0); // Normalized flag, currently not used.
65        writer.write_u8(att.att_data().get_id().as_usize() as u8); // unique id
66
67        // write the decoder type.
68        PortabilizationType::default_for(att.att_data().get_attribute_type()).write_to(writer);
69    }
70
71    // `adss` is built one-per-attribute and in the same order as `atts`, so each attribute is
72    // paired with its own attribute data structure here.
73    for ads in adss {
74        #[cfg(feature = "evaluation")]
75        eval::scope_begin("attribute", writer);
76
77        let parents_ids = ads.att_data().get_parents();
78        let parents = parents_ids
79            .iter()
80            .map(|id| port_atts.iter().find(|att| att.get_id() == *id).unwrap())
81            .collect::<Vec<_>>();
82
83        let ty = ads.att_data().get_attribute_type();
84        let len = ads.att_data().len();
85        let encoder = attribute_encoder::AttributeEncoder::new(
86            ads,
87            &parents,
88            &corners_of_edgebreaker,
89            writer,
90            cfg.attribute.encoder_config_for(ty, len),
91        );
92
93        let port_att = encoder.encode::<true, false>()?;
94        port_atts.push(port_att);
95
96        #[cfg(feature = "evaluation")]
97        eval::scope_end(writer);
98    }
99
100    #[cfg(feature = "evaluation")]
101    {
102        eval::array_scope_end(writer);
103        eval::scope_end(writer);
104    }
105
106    Ok(())
107}
108
109/// Per-attribute encoding configuration, keyed by attribute type. Any type
110/// without an explicit override falls back to the built-in `default_for(ty, len)`
111/// behaviour, so `Config::default()` reproduces the previous hardcoded pipeline.
112#[derive(Clone, Debug)]
113pub struct Config {
114    overrides: HashMap<AttributeType, AttributeConfig>,
115}
116
117/// Per-attribute-type encoding overrides. Every knob is optional; a `None` field
118/// keeps the built-in default for that attribute type, so a bare
119/// `AttributeConfig::default()` is a no-op. Invalid combinations (e.g. a texture
120/// predictor on a normal attribute) are representable here and rejected by
121/// [`Config::validate`](crate::encode::Config::validate).
122#[derive(Clone, Debug, Default)]
123pub struct AttributeConfig {
124    /// Prediction scheme override.
125    pub prediction: Option<PredictionSchemeType>,
126    /// Prediction transform override.
127    pub transform: Option<PredictionTransformType>,
128    /// Quantization resolution override.
129    pub quantization: Option<Quantization>,
130    /// Normal-specific encoding mode override (only valid for `Normal`).
131    pub normal_encoding: Option<NormalEncoding>,
132}
133
134/// How a normal attribute is compressed.
135#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Deserialize)]
136pub enum NormalEncoding {
137    /// Octahedrally quantize the input normals and encode the real octahedral
138    /// corrections (the default, lossy-by-quantization path).
139    #[default]
140    Quantized,
141    /// Zero-CPU: trust the decoder's geometry-derived prediction and emit an
142    /// all-zero correction stream. The input normal values are ignored, and
143    /// only their seams are used.
144    PredictedOnly,
145}
146
147impl ConfigType for Config {
148    fn default() -> Self {
149        Self {
150            overrides: HashMap::new(),
151        }
152    }
153}
154
155impl Config {
156    /// Overrides how normal attributes are compressed.
157    pub fn set_normal_encoding(&mut self, enc: NormalEncoding) {
158        self.overrides
159            .entry(AttributeType::Normal)
160            .or_default()
161            .normal_encoding = Some(enc);
162    }
163
164    /// Overrides the per-type encoding for `ty`, replacing any prior override.
165    pub fn set(&mut self, ty: AttributeType, cfg: AttributeConfig) {
166        self.overrides.insert(ty, cfg);
167    }
168
169    /// The current override for `ty` (a clone), or an empty default if none is
170    /// set. Useful for read-modify-write layering (e.g. a CLI flag patching a
171    /// single knob on top of a file-loaded config).
172    pub fn get(&self, ty: AttributeType) -> AttributeConfig {
173        self.overrides.get(&ty).cloned().unwrap_or_default()
174    }
175
176    /// The per-type overrides, for validation.
177    pub(crate) fn overrides(&self) -> &HashMap<AttributeType, AttributeConfig> {
178        &self.overrides
179    }
180
181    /// Resolves the per-attribute encoder config for an attribute of type `ty`
182    /// with `len` values, honoring any override and otherwise falling back to the
183    /// built-in default.
184    fn encoder_config_for(&self, ty: AttributeType, len: usize) -> attribute_encoder::Config {
185        let Some(over) = self.overrides.get(&ty) else {
186            return attribute_encoder::Config::default_for(ty, len);
187        };
188
189        // Start from the zero-correction base for PredictedOnly normals, else the
190        // regular per-type default; then patch in any explicit knobs.
191        let mut base = if over.normal_encoding == Some(NormalEncoding::PredictedOnly) {
192            attribute_encoder::Config::predicted_normals(len)
193        } else {
194            attribute_encoder::Config::default_for(ty, len)
195        };
196
197        if let Some(scheme) = &over.prediction {
198            base.set_prediction_scheme(scheme.clone());
199        }
200        if let Some(transform) = over.transform {
201            base.set_prediction_transform(transform);
202        }
203        if let Some(quant) = over.quantization {
204            base.set_quantization(quant);
205        }
206        base
207    }
208}
209
210#[remain::sorted]
211#[derive(thiserror::Error, Debug)]
212pub enum Err {
213    #[error("Attribute encoding error: {0}")]
214    AttributeError(#[from] attribute_encoder::Err),
215}