Skip to main content

DdsBase

Struct DdsBase 

Source
pub struct DdsBase<D = Vec<u8>> {
    pub header: Header,
    pub header10: Option<Header10>,
    pub data: D,
}
Expand description

This is the main DirectDraw Surface file structure, generic over how the payload is stored.

Use the aliases, not this type directly: Dds owns its payload and DdsView borrows one. Every query, surface, decode and upload-plan method is implemented once, here, for both.

Fields§

§header: Header§header10: Option<Header10>§data: D

Implementations§

Source§

impl<D: AsRef<[u8]>> DdsBase<D>

Source

pub fn is_cubemap(&self) -> bool

True when this DDS is a cubemap (DX10 TEXTURECUBE or legacy Caps2::CUBEMAP).

Source

pub fn subresource_layer_count(&self) -> u32

Number of selectable SubresourceId::layer values.

Cubemap: cube count. Non-cubemap: array layer count (or 1).

Source

pub fn subresource_face_count(&self) -> u32

Cubemap face count (6) or 1 for non-cubemaps.

Source

pub fn cube_count(&self) -> u32

Number of cube maps stored (DX10 array_size, or 1 for legacy cubemaps).

Source

pub fn physical_slice_count(&self) -> u32

Total physical mip-chains in data (array layers, or cubes × 6 faces).

Source

pub fn mip_dimensions(&self, mip: u32) -> Result<(u32, u32, u32), Error>

Width / height / depth of a mip level (each at least 1).

Source

pub fn subresource_range( &self, id: SubresourceId, ) -> Result<Range<usize>, Error>

Byte range of one subresource inside DdsBase::data.

Source

pub fn surface(&self, id: SubresourceId) -> Result<SurfaceView<'_>, Error>

Borrowed view of one subresource.

Source§

impl<D: AsRef<[u8]> + AsMut<[u8]>> DdsBase<D>

Mutable payload access. A crate::DdsView over &[u8] cannot satisfy AsMut, so these are available only when the payload is owned.

Source

pub fn surface_mut( &mut self, id: SubresourceId, ) -> Result<SurfaceViewMut<'_>, Error>

Mutable borrowed view of one subresource’s bytes (dimensions unchanged).

Source§

impl<D: AsRef<[u8]>> DdsBase<D>

Source

pub fn decode_content(&self) -> Result<DecodeContent, Error>

Classify this DDS for LDR RGBA8 decode/encode, if supported.

Source

pub fn hdr_decode_content(&self) -> Result<HdrDecodeContent, Error>

Classify this DDS for HDR float decode (crate::Dds::decode_rgba_f32).

Source§

impl<D: AsRef<[u8]>> DdsBase<D>

Source

pub fn decode_rgba8(&self, id: SubresourceId) -> Result<ImageRgba8, Error>

Available on crate feature decode only.

Decode one subresource to tightly packed RGBA8.

Volumes (depth > 1) decode every depth slice and stack them in ImageRgba8::pixels. sRGB-tagged formats return stored bytes without linearization.

§Channel conventions for BC4 and BC5

BC4 decodes to (R, 0, 0, 255) and BC5 to (R, G, 0, 255) — the values a GPU returns when sampling those formats, where the absent channels read as zero and alpha as one.

This differs from Microsoft DirectXTex, which replicates BC4’s single channel into green and blue, producing (R, R, R, 255). That is a greyscale-viewer convention: it makes a roughness or height map look right in an image preview. Measured over a 512^2 BC4 surface, the two agree on R and A for every one of 262 144 pixels and disagree on G and B for every one of them.

Neither is wrong; they answer different questions. If you are porting from DirectXTex::Decompress and your single-channel maps suddenly look red, this is why — replicate the red channel yourself. DirectXTex does not replicate for BC5, so only BC4 is affected.

Source§

impl<D: AsRef<[u8]>> DdsBase<D>

Source

pub fn decode_rgba_f32(&self, id: SubresourceId) -> Result<ImageRgbaF32, Error>

Available on crate feature decode only.

Decode one HDR subresource (BC6H) to tightly packed RGBA f32 (A = 1.0). Volumes decode every depth slice, stacked. LDR content stays on DdsBase::decode_rgba8; each API fails closed on the other’s formats.

Source

pub fn decode_rgba_f32_into( &self, id: SubresourceId, dst: &mut Vec<f32>, ) -> Result<(u32, u32, u32), Error>

Available on crate feature decode only.

Decode one HDR subresource to RGBA f32 into a buffer you own.

The HDR twin of DdsBase::decode_rgba8_into, and it matters more here: this output is 16 bytes a pixel, four times RGBA8, so a 1024^2 surface hands back 16 MiB that the operating system zeroed for you and the decoder immediately overwrote. Recycle one buffer per worker instead.

dst is resized to fit and fully overwritten. Returns (width, height, depth).

Source

pub fn decode_block_rows_f32_into( &self, id: SubresourceId, rows: Range<u32>, dst: &mut [f32], ) -> Result<(), Error>

Available on crate feature decode only.

Decode a range of block rows of one HDR subresource into caller memory — the seam your job system splits on.

BC6H is the most expensive decode this crate ships: at 1024^2 it is ~21 ms serial, against 2.6 ms for BC7 split across a caller’s threads. Unlike BC7 it has no internal thread pool to fall back on, and it should not grow one — a texture library that seizes cores inside a frame is a library an engine has to work around. This hands you the split instead.

rows is in block rows of 4 pixel rows each; dst receives only those rows, tightly packed at width * 4 floats per pixel row. Use DdsBase::block_rows_f32 for the count.

§Split only what is worth splitting

Do not split a whole mip chain. Measured over a cooked 512^2 pack, splitting every level across 24 threads is a net 0.53x — slower than serial — because a ten-level chain is mostly small mips, and entering std::thread::scope costs ~50 us even to spawn a single worker. That is more than the entire decode of every level past mip 4.

Split above roughly 16 384 blocks (512x512) and decode the rest on the calling thread. Same pack, same threads, that one rule turns 0.53x into 1.35x; at 1024^2 mip 0 the split alone is worth 6.8x.

let blocks = w.div_ceil(4) * h.div_ceil(4);
if blocks >= 16_384 { /* fan out */ } else { /* decode here */ }
let id = SubresourceId::mip_layer(0, 0);
let rows = dds.block_rows_f32(id)?;
let (w, h, _) = (1024usize, 1024usize, 1);
let mut pixels = vec![0f32; w * h * 4];
let (top, bottom) = pixels.split_at_mut(w * (h / 2) * 4);
// In a real engine these two are two jobs on two cores.
dds.decode_block_rows_f32_into(id, 0..rows / 2, top)?;
dds.decode_block_rows_f32_into(id, rows / 2..rows, bottom)?;
Source

pub fn block_rows_f32(&self, id: SubresourceId) -> Result<u32, Error>

Available on crate feature decode only.

Block rows in one HDR subresource — the unit DdsBase::decode_block_rows_f32_into splits on. Always 4 pixel rows.

Source§

impl<D: AsRef<[u8]>> DdsBase<D>

Source

pub fn decode_rgba8_into( &self, id: SubresourceId, dst: &mut Vec<u8>, ) -> Result<(u32, u32, u32), Error>

Available on crate feature decode only.

Decode one subresource to RGBA8 into a buffer you own and recycle.

DdsBase::decode_rgba8 allocates a fresh Vec every call. That buffer is handed over zeroed by the operating system and then immediately overwritten by the decoder, and on a 1024^2 BC7 surface that measured at 41% of the whole call (1.44 ms of 3.52 ms). Reuse one buffer per worker and the cost is the decode alone.

dst is resized to fit and fully overwritten. Returns (width, height, depth).

let dds = DdsView::parse(&bytes)?;
let mut pixels = Vec::new();          // hoisted out of the loop
let (w, h, _) = dds.decode_rgba8_into(SubresourceId::mip_layer(0, 0), &mut pixels)?;
assert_eq!(pixels.len(), (w * h * 4) as usize);
Source

pub fn decode_block_rows_into( &self, id: SubresourceId, rows: Range<u32>, dst: &mut [u8], ) -> Result<(), Error>

Available on crate feature decode only.

Decode a range of block-rows of one subresource into caller memory.

This is the seam for a caller that already has a job system. rusty_dds spawns one thread per core inside decode_bc7, and that costs ~0.98 ms per call on a 24-core box whatever the work — 34% of a 1024^2 BC7 decode, and more than the entire cost of the equivalent BC1 decode. Splitting the surface here instead means the library owns no threads at all and your scheduler keeps its cores.

rows is in block rows (4 pixel rows each for BCn, 1 for uncompressed formats); dst receives only those rows, tightly packed at width * 4 bytes per pixel row. Use DdsBase::block_rows for the count.

let dds = DdsView::parse(&bytes)?;
let id = SubresourceId::mip_layer(0, 0);
let rows = dds.block_rows(id)?;                 // 16 block rows for 64px
let mut pixels = vec![0u8; 64 * 64 * 4];
// Two halves — in a real engine these are two jobs on two cores.
let (top, bottom) = pixels.split_at_mut(64 * 32 * 4);
dds.decode_block_rows_into(id, 0..rows / 2, top)?;
dds.decode_block_rows_into(id, rows / 2..rows, bottom)?;
Source

pub fn block_rows(&self, id: SubresourceId) -> Result<u32, Error>

Available on crate feature decode only.

Block rows in one subresource — the unit DdsBase::decode_block_rows_into splits on. Four pixel rows each for BCn, one for uncompressed formats.

Source§

impl DdsBase

Source

pub fn encode_from_rgba8( pixels: &[u8], layout: EncodeLayout, ) -> Result<Dds, Error>

Available on crate feature encode only.

Encode tightly packed RGBA8 pixels into a new DDS matching EncodeLayout.

Source layout (mip 0 only; extra mips are box-filtered):

  • 2D: width * height * 4
  • Array: layers stacked
  • Cubemap: faces in DirectX order, then cubes
  • Volume: depth slices stacked (z major after rows)
Source§

impl DdsBase

Source

pub fn encode_bc6h_uf16( pixels: &[f32], width: u32, height: u32, ) -> Result<Dds, Error>

Available on crate feature encode only.

Encode tightly packed RGBA f32 pixels (alpha ignored) as a 2D BC6H_UF16 DDS (mode 11: single subset, 10-bit endpoints, 4-bit indices). Negative / NaN inputs clamp to 0, values above the half range clamp to 65504. Round-trips through Dds::decode_rgba_f32.

Source§

impl<D: AsRef<[u8]>> DdsBase<D>

Source

pub fn gpu_format(&self) -> Result<GpuFormat, Error>

Map this DDS to a GpuFormat for compressed GPU upload, if supported.

Source

pub fn upload_plan_compressed( &self, id: SubresourceId, ) -> Result<UploadPlan, Error>

Plan a compressed upload of one subresource (Path A).

Source

pub fn upload_plan_decoded_rgba8( &self, id: SubresourceId, ) -> Result<UploadPlan, Error>

Plan a decoded RGBA8 upload (Path B).

data_offset is 0; data_len is width * height * depth * 4. Call Self::decode_rgba8 for the bytes. Format is always RGBA8 UNORM (stored sRGB bytes are not linearized — same policy as decode).

Source§

impl DdsBase

Source

pub fn new_d3d(params: NewD3dParams) -> Result<Dds, Error>

Create a new DirectDraw Surface with a D3DFormat

Source

pub fn new_dxgi(params: NewDxgiParams) -> Result<Dds, Error>

Create a new DirectDraw Surface with a DxgiFormat

Source

pub fn read<R: Read>(r: R) -> Result<Dds, Error>

Read a DDS file, accepting a payload of any length.

The payload is read to end-of-stream with no cap, so the peak allocation is whatever the reader yields. That is the right behaviour for a trusted file on disk and the wrong one for bytes arriving from a network, a user upload, or a mod archive — for those, use Dds::read_limited, which fails closed at a byte budget you choose.

Source

pub fn read_limited<R: Read>(r: R, max_data_len: usize) -> Result<Dds, Error>

Read a DDS file, refusing a payload larger than max_data_len bytes.

The limit covers the payload only — the 128-byte header (148 with a DX10 header) is read first and is not counted. Exceeding it returns Error::SizeLimitExceeded without buffering the overrun, so a hostile or corrupt stream cannot force an unbounded allocation.

use rusty_dds::{Dds, Error};

let mut bytes = Vec::new();
Dds::new_dxgi(rusty_dds::NewDxgiParams {
    height: 64, width: 64, depth: None,
    format: rusty_dds::DxgiFormat::BC1_UNorm,
    mipmap_levels: None, array_layers: None, caps2: None, is_cubemap: false,
    resource_dimension: rusty_dds::D3D10ResourceDimension::Texture2D,
    alpha_mode: rusty_dds::AlphaMode::Straight,
})?.write(&mut bytes)?;

assert!(Dds::read_limited(&bytes[..], 8 * 1024).is_ok());
assert!(matches!(
    Dds::read_limited(&bytes[..], 16),
    Err(Error::SizeLimitExceeded { .. })
));
Source§

impl<'a> DdsBase<&'a [u8]>

Source

pub fn parse(bytes: &'a [u8]) -> Result<DdsView<'a>, Error>

Parse a DDS without copying the payload.

The returned view borrows bytes for its lifetime. Everything a streaming engine needs — DdsBase::surface, DdsBase::subresource_range, DdsBase::upload_plan_compressed, decode — works on it exactly as it does on an owned Dds.

Source

pub fn read_into<R: Read>( r: R, buf: &'a mut Vec<u8>, ) -> Result<DdsView<'a>, Error>

Read a DDS from any reader into a buffer you own and recycle.

DdsView::parse is the right call when you already hold the bytes. This is for the case where you do not — an archive decompressor, a network stream — and would otherwise be forced back onto Dds::read, which allocates a fresh payload buffer every time. A fresh buffer is faulted in and zeroed by the operating system before it is overwritten, which measured at ~87% of that call; reusing one buffer keeps the pages resident and the cost is the copy alone.

buf is cleared, so its capacity survives and the second call onwards touches no new pages. Reuse one buffer per streaming worker.

use rusty_dds::{DdsView, SubresourceId};

let mut buf = Vec::new();          // hoisted out of the loop
for _ in 0..2 {
    let dds = DdsView::read_into(&bytes[..], &mut buf)?;
    assert_eq!(dds.get_width(), 64);
}
Source

pub fn read_into_limited<R: Read>( r: R, buf: &'a mut Vec<u8>, max_data_len: usize, ) -> Result<DdsView<'a>, Error>

DdsView::read_into, refusing a payload larger than max_data_len.

Same posture as Dds::read_limited: the limit covers the payload only, and an overrun fails closed without buffering the rest. Use this for bytes you did not produce — a mod archive, a download.

Source§

impl<D: AsRef<[u8]>> DdsBase<D>

Source

pub fn write<W: Write>(&self, w: &mut W) -> Result<(), Error>

Write to a DDS file

Source

pub fn get_d3d_format(&self) -> Option<D3DFormat>

Attempt to get the format of this DDS, presuming it is a D3DFormat.

Source

pub fn get_dxgi_format(&self) -> Option<DxgiFormat>

Attempt to get the format of this DDS, presuming it is a DxgiFormat.

Source

pub fn get_format(&self) -> Option<Box<dyn DataFormat>>

Get the format of the DDS as a trait (type-erasure)

Source

pub fn get_width(&self) -> u32

Source

pub fn get_height(&self) -> u32

Source

pub fn get_depth(&self) -> u32

Source

pub fn get_bits_per_pixel(&self) -> Option<u32>

Source

pub fn get_pitch(&self) -> Option<u32>

Source

pub fn get_pitch_height(&self) -> u32

Source

pub fn get_main_texture_size(&self) -> Option<u32>

Source

pub fn get_array_stride(&self) -> Result<u32, Error>

Source

pub fn get_num_array_layers(&self) -> u32

Source

pub fn get_num_mipmap_levels(&self) -> u32

Source

pub fn get_min_mipmap_size_in_bytes(&self) -> u32

Source

pub fn get_data(&self, array_layer: u32) -> Result<&[u8], Error>

This gets a reference to the data at the given array_layer (which should be 0 for textures with just one image).

Source§

impl<D: AsRef<[u8]> + AsMut<[u8]>> DdsBase<D>

Source

pub fn get_mut_data(&mut self, array_layer: u32) -> Result<&mut [u8], Error>

This gets a mutable reference to the data at the given array_layer (which should be 0 for textures with just one image).

Only available when the payload is owned or mutably borrowed — a DdsView over &[u8] cannot offer it.

Trait Implementations§

Source§

impl<D: Clone> Clone for DdsBase<D>

Source§

fn clone(&self) -> DdsBase<D>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<D: AsRef<[u8]>> Debug for DdsBase<D>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<D> Freeze for DdsBase<D>
where D: Freeze,

§

impl<D> RefUnwindSafe for DdsBase<D>
where D: RefUnwindSafe,

§

impl<D> Send for DdsBase<D>
where D: Send,

§

impl<D> Sync for DdsBase<D>
where D: Sync,

§

impl<D> Unpin for DdsBase<D>
where D: Unpin,

§

impl<D> UnsafeUnpin for DdsBase<D>
where D: UnsafeUnpin,

§

impl<D> UnwindSafe for DdsBase<D>
where D: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.