Skip to main content

rusty_dds/
surface.rs

1// The MIT License (MIT)
2//
3// Copyright (c) 2018 Michael Dilger
4// Copyright (c) 2026 Remade With Rust / Mata Network
5//
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to deal
8// in the Software without restriction, including without limitation the rights
9// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10// copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22// THE SOFTWARE.
23
24//! Typed views into a DDS payload: mip / array layer / cubemap face.
25
26use crate::error::Error;
27use crate::header::Caps2;
28use crate::header10::MiscFlag;
29use crate::DdsBase;
30use std::ops::Range;
31
32/// DirectX cubemap face order (face index 0..5).
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34#[repr(u32)]
35pub enum CubemapFace {
36    PositiveX = 0,
37    NegativeX = 1,
38    PositiveY = 2,
39    NegativeY = 3,
40    PositiveZ = 4,
41    NegativeZ = 5,
42}
43
44impl CubemapFace {
45    pub const ALL: [CubemapFace; 6] = [
46        CubemapFace::PositiveX,
47        CubemapFace::NegativeX,
48        CubemapFace::PositiveY,
49        CubemapFace::NegativeY,
50        CubemapFace::PositiveZ,
51        CubemapFace::NegativeZ,
52    ];
53
54    pub fn from_index(index: u32) -> Result<CubemapFace, Error> {
55        match index {
56            0 => Ok(CubemapFace::PositiveX),
57            1 => Ok(CubemapFace::NegativeX),
58            2 => Ok(CubemapFace::PositiveY),
59            3 => Ok(CubemapFace::NegativeY),
60            4 => Ok(CubemapFace::PositiveZ),
61            5 => Ok(CubemapFace::NegativeZ),
62            _ => Err(Error::OutOfBounds),
63        }
64    }
65
66    pub fn index(self) -> u32 {
67        self as u32
68    }
69}
70
71/// Identifies one mip / layer / face subresource inside [`DdsBase::data`](crate::DdsBase::data).
72///
73/// - `mip` — mip level (0 = largest)
74/// - `layer` — array layer, or cube index for cubemaps
75/// - `face` — cubemap face 0..5 ([`CubemapFace`]); must be `0` for non-cubemaps
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
77pub struct SubresourceId {
78    pub mip: u32,
79    pub layer: u32,
80    pub face: u32,
81}
82
83impl SubresourceId {
84    pub const fn new(mip: u32, layer: u32, face: u32) -> Self {
85        Self { mip, layer, face }
86    }
87
88    pub const fn mip_layer(mip: u32, layer: u32) -> Self {
89        Self {
90            mip,
91            layer,
92            face: 0,
93        }
94    }
95
96    pub const fn cubemap(mip: u32, layer: u32, face: CubemapFace) -> Self {
97        Self {
98            mip,
99            layer,
100            face: face as u32,
101        }
102    }
103}
104
105/// Borrowed view of one subresource's bytes and mip dimensions.
106#[derive(Debug, Clone, Copy)]
107#[non_exhaustive]
108pub struct SurfaceView<'a> {
109    pub id: SubresourceId,
110    pub width: u32,
111    pub height: u32,
112    pub depth: u32,
113    pub data: &'a [u8],
114}
115
116impl<D: AsRef<[u8]>> DdsBase<D> {
117    /// True when this DDS is a cubemap (DX10 `TEXTURECUBE` or legacy `Caps2::CUBEMAP`).
118    pub fn is_cubemap(&self) -> bool {
119        if let Some(ref h10) = self.header10 {
120            h10.misc_flag.contains(MiscFlag::TEXTURECUBE)
121        } else {
122            self.header.caps2.contains(Caps2::CUBEMAP)
123        }
124    }
125
126    /// Number of selectable `SubresourceId::layer` values.
127    ///
128    /// Cubemap: cube count. Non-cubemap: array layer count (or `1`).
129    pub fn subresource_layer_count(&self) -> u32 {
130        if self.is_cubemap() {
131            self.cube_count()
132        } else if let Some(ref h10) = self.header10 {
133            h10.array_size.max(1)
134        } else {
135            1
136        }
137    }
138
139    /// Cubemap face count (`6`) or `1` for non-cubemaps.
140    pub fn subresource_face_count(&self) -> u32 {
141        if self.is_cubemap() {
142            6
143        } else {
144            1
145        }
146    }
147
148    /// Number of cube maps stored (DX10 `array_size`, or `1` for legacy cubemaps).
149    pub fn cube_count(&self) -> u32 {
150        if !self.is_cubemap() {
151            return 0;
152        }
153        if let Some(ref h10) = self.header10 {
154            h10.array_size.max(1)
155        } else {
156            1
157        }
158    }
159
160    /// Total physical mip-chains in `data` (array layers, or cubes × 6 faces).
161    pub fn physical_slice_count(&self) -> u32 {
162        if self.is_cubemap() {
163            self.cube_count().saturating_mul(6)
164        } else if let Some(ref h10) = self.header10 {
165            h10.array_size.max(1)
166        } else {
167            1
168        }
169    }
170
171    /// Width / height / depth of a mip level (each at least 1).
172    pub fn mip_dimensions(&self, mip: u32) -> Result<(u32, u32, u32), Error> {
173        if mip >= self.get_num_mipmap_levels() {
174            return Err(Error::OutOfBounds);
175        }
176        let width = (self.header.width >> mip).max(1);
177        let height = (self.header.height >> mip).max(1);
178        let depth = (self.header.depth.unwrap_or(1) >> mip).max(1);
179        Ok((width, height, depth))
180    }
181
182    /// Byte range of one subresource inside [`DdsBase::data`](crate::DdsBase::data).
183    pub fn subresource_range(&self, id: SubresourceId) -> Result<Range<usize>, Error> {
184        let (offset, size) = self.subresource_offset_and_size(id)?;
185        let start = offset as usize;
186        let end = start
187            .checked_add(size as usize)
188            .ok_or(Error::OutOfBounds)?;
189        if end > self.data.as_ref().len() {
190            return Err(Error::TruncatedData);
191        }
192        Ok(start..end)
193    }
194
195    /// Borrowed view of one subresource.
196    pub fn surface(&self, id: SubresourceId) -> Result<SurfaceView<'_>, Error> {
197        let range = self.subresource_range(id)?;
198        let (width, height, depth) = self.mip_dimensions(id.mip)?;
199        Ok(SurfaceView {
200            id,
201            width,
202            height,
203            depth,
204            data: &self.data.as_ref()[range],
205        })
206    }
207
208    /// Byte ranges of EVERY mip of one physical slice, in ONE chain walk.
209    ///
210    /// The per-call form re-walks the chain from the top for each level
211    /// (`mip_offset_and_size_in_chain` is O(levels)), which makes a
212    /// level-by-level consumer O(levels²) per slice — the encoder's mip
213    /// driver was exactly that consumer.
214    pub(crate) fn subresource_chain_ranges(
215        &self,
216        layer: u32,
217        face: u32,
218    ) -> Result<Vec<Range<usize>>, Error> {
219        let levels = self.get_num_mipmap_levels();
220        let id0 = SubresourceId::new(0, layer, face);
221        self.validate_subresource_id(id0)?;
222        let physical = self.physical_slice_index(id0)?;
223        let array_stride = self.get_array_stride()?;
224        let base = physical
225            .checked_mul(array_stride)
226            .ok_or(Error::OutOfBounds)?;
227        let mut current = self
228            .get_main_texture_size()
229            .ok_or(Error::UnsupportedFormat)?;
230        let min_size = self.get_min_mipmap_size_in_bytes();
231        let mut offset = base;
232        let data_len = self.data.as_ref().len();
233        let mut out = Vec::with_capacity(levels as usize);
234        for _ in 0..levels {
235            let start = offset as usize;
236            let end = start
237                .checked_add(current as usize)
238                .ok_or(Error::OutOfBounds)?;
239            if end > data_len {
240                return Err(Error::TruncatedData);
241            }
242            out.push(start..end);
243            offset = offset.checked_add(current).ok_or(Error::OutOfBounds)?;
244            current /= 4;
245            if current < min_size {
246                current = min_size;
247            }
248        }
249        Ok(out)
250    }
251
252    fn subresource_offset_and_size(&self, id: SubresourceId) -> Result<(u32, u32), Error> {
253        self.validate_subresource_id(id)?;
254
255        let physical = self.physical_slice_index(id)?;
256        let array_stride = self.get_array_stride()?;
257        let (mip_offset, mip_size) = self.mip_offset_and_size_in_chain(id.mip)?;
258
259        let offset = physical
260            .checked_mul(array_stride)
261            .and_then(|base| base.checked_add(mip_offset))
262            .ok_or(Error::OutOfBounds)?;
263
264        Ok((offset, mip_size))
265    }
266
267    fn validate_subresource_id(&self, id: SubresourceId) -> Result<(), Error> {
268        if id.mip >= self.get_num_mipmap_levels() {
269            return Err(Error::OutOfBounds);
270        }
271        if id.layer >= self.subresource_layer_count() {
272            return Err(Error::OutOfBounds);
273        }
274        if self.is_cubemap() {
275            if id.face >= 6 {
276                return Err(Error::OutOfBounds);
277            }
278        } else if id.face != 0 {
279            return Err(Error::OutOfBounds);
280        }
281        Ok(())
282    }
283
284    fn physical_slice_index(&self, id: SubresourceId) -> Result<u32, Error> {
285        if self.is_cubemap() {
286            id.layer
287                .checked_mul(6)
288                .and_then(|base| base.checked_add(id.face))
289                .ok_or(Error::OutOfBounds)
290        } else {
291            Ok(id.layer)
292        }
293    }
294
295    /// Mip offset/size within one physical slice, matching [`Dds::get_array_stride`]'s chain.
296    fn mip_offset_and_size_in_chain(&self, mip: u32) -> Result<(u32, u32), Error> {
297        let levels = self.get_num_mipmap_levels();
298        if mip >= levels {
299            return Err(Error::OutOfBounds);
300        }
301        let mut current = self
302            .get_main_texture_size()
303            .ok_or(Error::UnsupportedFormat)?;
304        let min_size = self.get_min_mipmap_size_in_bytes();
305        let mut offset = 0_u32;
306        for level in 0..levels {
307            if level == mip {
308                return Ok((offset, current));
309            }
310            offset = offset.checked_add(current).ok_or(Error::OutOfBounds)?;
311            current /= 4;
312            if current < min_size {
313                current = min_size;
314            }
315        }
316        Err(Error::OutOfBounds)
317    }
318}
319
320/// Mutable payload access. A [`crate::DdsView`] over `&[u8]` cannot satisfy
321/// `AsMut`, so these are available only when the payload is owned.
322impl<D: AsRef<[u8]> + AsMut<[u8]>> DdsBase<D> {
323    /// Mutable borrowed view of one subresource's bytes (dimensions unchanged).
324    pub fn surface_mut(&mut self, id: SubresourceId) -> Result<SurfaceViewMut<'_>, Error> {
325        let range = self.subresource_range(id)?;
326        let (width, height, depth) = self.mip_dimensions(id.mip)?;
327        Ok(SurfaceViewMut {
328            id,
329            width,
330            height,
331            depth,
332            data: &mut self.data.as_mut()[range],
333        })
334    }
335}
336
337/// Mutable borrowed view of one subresource.
338#[derive(Debug)]
339#[non_exhaustive]
340pub struct SurfaceViewMut<'a> {
341    pub id: SubresourceId,
342    pub width: u32,
343    pub height: u32,
344    pub depth: u32,
345    pub data: &'a mut [u8],
346}