pub struct DdsBase<D = Vec<u8>> {
pub header: Header,
pub header10: Option<Header10>,
pub data: D,
}Expand description
Fields§
§header: Header§header10: Option<Header10>§data: DImplementations§
Source§impl<D: AsRef<[u8]>> DdsBase<D>
impl<D: AsRef<[u8]>> DdsBase<D>
Sourcepub fn is_cubemap(&self) -> bool
pub fn is_cubemap(&self) -> bool
True when this DDS is a cubemap (DX10 TEXTURECUBE or legacy Caps2::CUBEMAP).
Sourcepub fn subresource_layer_count(&self) -> u32
pub fn subresource_layer_count(&self) -> u32
Number of selectable SubresourceId::layer values.
Cubemap: cube count. Non-cubemap: array layer count (or 1).
Sourcepub fn subresource_face_count(&self) -> u32
pub fn subresource_face_count(&self) -> u32
Cubemap face count (6) or 1 for non-cubemaps.
Sourcepub fn cube_count(&self) -> u32
pub fn cube_count(&self) -> u32
Number of cube maps stored (DX10 array_size, or 1 for legacy cubemaps).
Sourcepub fn physical_slice_count(&self) -> u32
pub fn physical_slice_count(&self) -> u32
Total physical mip-chains in data (array layers, or cubes × 6 faces).
Sourcepub fn mip_dimensions(&self, mip: u32) -> Result<(u32, u32, u32), Error>
pub fn mip_dimensions(&self, mip: u32) -> Result<(u32, u32, u32), Error>
Width / height / depth of a mip level (each at least 1).
Sourcepub fn subresource_range(
&self,
id: SubresourceId,
) -> Result<Range<usize>, Error>
pub fn subresource_range( &self, id: SubresourceId, ) -> Result<Range<usize>, Error>
Byte range of one subresource inside DdsBase::data.
Sourcepub fn surface(&self, id: SubresourceId) -> Result<SurfaceView<'_>, Error>
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.
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.
Sourcepub fn surface_mut(
&mut self,
id: SubresourceId,
) -> Result<SurfaceViewMut<'_>, Error>
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>
impl<D: AsRef<[u8]>> DdsBase<D>
Sourcepub fn decode_content(&self) -> Result<DecodeContent, Error>
pub fn decode_content(&self) -> Result<DecodeContent, Error>
Classify this DDS for LDR RGBA8 decode/encode, if supported.
Sourcepub fn hdr_decode_content(&self) -> Result<HdrDecodeContent, Error>
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>
impl<D: AsRef<[u8]>> DdsBase<D>
Sourcepub fn decode_rgba8(&self, id: SubresourceId) -> Result<ImageRgba8, Error>
Available on crate feature decode only.
pub fn decode_rgba8(&self, id: SubresourceId) -> Result<ImageRgba8, Error>
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>
impl<D: AsRef<[u8]>> DdsBase<D>
Sourcepub fn decode_rgba_f32(&self, id: SubresourceId) -> Result<ImageRgbaF32, Error>
Available on crate feature decode only.
pub fn decode_rgba_f32(&self, id: SubresourceId) -> Result<ImageRgbaF32, Error>
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.
Sourcepub fn decode_rgba_f32_into(
&self,
id: SubresourceId,
dst: &mut Vec<f32>,
) -> Result<(u32, u32, u32), Error>
Available on crate feature decode only.
pub fn decode_rgba_f32_into( &self, id: SubresourceId, dst: &mut Vec<f32>, ) -> Result<(u32, u32, u32), Error>
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).
Sourcepub fn decode_block_rows_f32_into(
&self,
id: SubresourceId,
rows: Range<u32>,
dst: &mut [f32],
) -> Result<(), Error>
Available on crate feature decode only.
pub fn decode_block_rows_f32_into( &self, id: SubresourceId, rows: Range<u32>, dst: &mut [f32], ) -> Result<(), Error>
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)?;Sourcepub fn block_rows_f32(&self, id: SubresourceId) -> Result<u32, Error>
Available on crate feature decode only.
pub fn block_rows_f32(&self, id: SubresourceId) -> Result<u32, Error>
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>
impl<D: AsRef<[u8]>> DdsBase<D>
Sourcepub fn decode_rgba8_into(
&self,
id: SubresourceId,
dst: &mut Vec<u8>,
) -> Result<(u32, u32, u32), Error>
Available on crate feature decode only.
pub fn decode_rgba8_into( &self, id: SubresourceId, dst: &mut Vec<u8>, ) -> Result<(u32, u32, u32), Error>
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);Sourcepub fn decode_block_rows_into(
&self,
id: SubresourceId,
rows: Range<u32>,
dst: &mut [u8],
) -> Result<(), Error>
Available on crate feature decode only.
pub fn decode_block_rows_into( &self, id: SubresourceId, rows: Range<u32>, dst: &mut [u8], ) -> Result<(), Error>
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)?;Sourcepub fn block_rows(&self, id: SubresourceId) -> Result<u32, Error>
Available on crate feature decode only.
pub fn block_rows(&self, id: SubresourceId) -> Result<u32, Error>
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
impl DdsBase
Sourcepub fn encode_from_rgba8(
pixels: &[u8],
layout: EncodeLayout,
) -> Result<Dds, Error>
Available on crate feature encode only.
pub fn encode_from_rgba8( pixels: &[u8], layout: EncodeLayout, ) -> Result<Dds, Error>
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 (
zmajor after rows)
Source§impl DdsBase
impl DdsBase
Sourcepub fn encode_bc6h_uf16(
pixels: &[f32],
width: u32,
height: u32,
) -> Result<Dds, Error>
Available on crate feature encode only.
pub fn encode_bc6h_uf16( pixels: &[f32], width: u32, height: u32, ) -> Result<Dds, Error>
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>
impl<D: AsRef<[u8]>> DdsBase<D>
Sourcepub fn gpu_format(&self) -> Result<GpuFormat, Error>
pub fn gpu_format(&self) -> Result<GpuFormat, Error>
Map this DDS to a GpuFormat for compressed GPU upload, if supported.
Sourcepub fn upload_plan_compressed(
&self,
id: SubresourceId,
) -> Result<UploadPlan, Error>
pub fn upload_plan_compressed( &self, id: SubresourceId, ) -> Result<UploadPlan, Error>
Plan a compressed upload of one subresource (Path A).
Sourcepub fn upload_plan_decoded_rgba8(
&self,
id: SubresourceId,
) -> Result<UploadPlan, Error>
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
impl DdsBase
Sourcepub fn new_d3d(params: NewD3dParams) -> Result<Dds, Error>
pub fn new_d3d(params: NewD3dParams) -> Result<Dds, Error>
Create a new DirectDraw Surface with a D3DFormat
Sourcepub fn new_dxgi(params: NewDxgiParams) -> Result<Dds, Error>
pub fn new_dxgi(params: NewDxgiParams) -> Result<Dds, Error>
Create a new DirectDraw Surface with a DxgiFormat
Sourcepub fn read<R: Read>(r: R) -> Result<Dds, Error>
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.
Sourcepub fn read_limited<R: Read>(r: R, max_data_len: usize) -> Result<Dds, Error>
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]>
impl<'a> DdsBase<&'a [u8]>
Sourcepub fn parse(bytes: &'a [u8]) -> Result<DdsView<'a>, Error>
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.
Sourcepub fn read_into<R: Read>(
r: R,
buf: &'a mut Vec<u8>,
) -> Result<DdsView<'a>, Error>
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);
}Sourcepub fn read_into_limited<R: Read>(
r: R,
buf: &'a mut Vec<u8>,
max_data_len: usize,
) -> Result<DdsView<'a>, Error>
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>
impl<D: AsRef<[u8]>> DdsBase<D>
Sourcepub fn get_d3d_format(&self) -> Option<D3DFormat>
pub fn get_d3d_format(&self) -> Option<D3DFormat>
Attempt to get the format of this DDS, presuming it is a D3DFormat.
Sourcepub fn get_dxgi_format(&self) -> Option<DxgiFormat>
pub fn get_dxgi_format(&self) -> Option<DxgiFormat>
Attempt to get the format of this DDS, presuming it is a DxgiFormat.
Sourcepub fn get_format(&self) -> Option<Box<dyn DataFormat>>
pub fn get_format(&self) -> Option<Box<dyn DataFormat>>
Get the format of the DDS as a trait (type-erasure)
pub fn get_width(&self) -> u32
pub fn get_height(&self) -> u32
pub fn get_depth(&self) -> u32
pub fn get_bits_per_pixel(&self) -> Option<u32>
pub fn get_pitch(&self) -> Option<u32>
pub fn get_pitch_height(&self) -> u32
pub fn get_main_texture_size(&self) -> Option<u32>
pub fn get_array_stride(&self) -> Result<u32, Error>
pub fn get_num_array_layers(&self) -> u32
pub fn get_num_mipmap_levels(&self) -> u32
pub fn get_min_mipmap_size_in_bytes(&self) -> u32
Source§impl<D: AsRef<[u8]> + AsMut<[u8]>> DdsBase<D>
impl<D: AsRef<[u8]> + AsMut<[u8]>> DdsBase<D>
Sourcepub fn get_mut_data(&mut self, array_layer: u32) -> Result<&mut [u8], Error>
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.