use crate::{DType, PixelFormat};
pub const DXGI_FORMAT_R32G32B32A32_FLOAT: u32 = 2;
pub const DXGI_FORMAT_R16G16B16A16_FLOAT: u32 = 10;
pub const DXGI_FORMAT_R8G8B8A8_UNORM: u32 = 28;
pub const DXGI_FORMAT_R8G8_UNORM: u32 = 49;
pub const DXGI_FORMAT_R8_UNORM: u32 = 61;
pub const DXGI_FORMAT_B8G8R8A8_UNORM: u32 = 87;
pub const GL_RGBA: u32 = 0x1908;
pub const GL_BGRA_EXT: u32 = 0x80E1;
pub const GL_RED_EXT: u32 = 0x1903;
pub const GL_RG_EXT: u32 = 0x8227;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct D3d11ImageLayout {
pub dxgi_format: u32,
pub texture_width: usize,
pub texture_height: usize,
pub bytes_per_texel: usize,
pub gl_internal_format: u32,
}
impl D3d11ImageLayout {
pub fn tight_row_bytes(&self) -> usize {
self.texture_width * self.bytes_per_texel
}
pub fn tight_bytes(&self) -> usize {
self.tight_row_bytes() * self.texture_height
}
pub fn widened_to(&self, texture_width: usize) -> Self {
Self {
texture_width: texture_width.max(self.texture_width),
..*self
}
}
}
fn lay(dxgi_format: u32, w: usize, h: usize, bpt: usize, gl: u32) -> Option<D3d11ImageLayout> {
Some(D3d11ImageLayout {
dxgi_format,
texture_width: w,
texture_height: h,
bytes_per_texel: bpt,
gl_internal_format: gl,
})
}
pub fn image_d3d11_layout(
format: PixelFormat,
dtype: DType,
width: usize,
height: usize,
) -> Option<D3d11ImageLayout> {
use PixelFormat::*;
if width == 0 || height == 0 {
return None;
}
let quarter = |n: usize| {
if n.is_multiple_of(4) {
Some(n / 4)
} else {
None
}
};
match (format, dtype) {
(Rgba, DType::U8 | DType::I8) => lay(DXGI_FORMAT_R8G8B8A8_UNORM, width, height, 4, GL_RGBA),
(Bgra, DType::U8) => lay(DXGI_FORMAT_B8G8R8A8_UNORM, width, height, 4, GL_BGRA_EXT),
(Rgb, DType::U8 | DType::I8) => lay(
DXGI_FORMAT_R8G8B8A8_UNORM,
quarter(width * 3)?,
height,
4,
GL_RGBA,
),
(Grey, DType::U8) => lay(DXGI_FORMAT_R8_UNORM, width, height, 1, GL_RED_EXT),
(Nv12 | Nv16 | Nv24, DType::U8) => {
let shape = format.allocation_shape(width, height)?;
lay(
DXGI_FORMAT_R8_UNORM,
shape[1].next_multiple_of(2),
shape[0],
1,
GL_RED_EXT,
)
}
(Yuyv, DType::U8) => lay(DXGI_FORMAT_R8G8_UNORM, width, height, 2, GL_RG_EXT),
(PlanarRgb | PlanarRgba, DType::F16) => lay(
DXGI_FORMAT_R16G16B16A16_FLOAT,
quarter(width)?,
format.channels() * height,
8,
GL_RGBA,
),
(PlanarRgb | PlanarRgba, DType::F32) => lay(
DXGI_FORMAT_R32G32B32A32_FLOAT,
quarter(width)?,
format.channels() * height,
16,
GL_RGBA,
),
(Rgb, DType::F16) => lay(
DXGI_FORMAT_R16G16B16A16_FLOAT,
quarter(width * 3)?,
height,
8,
GL_RGBA,
),
(Rgb, DType::F32) => lay(
DXGI_FORMAT_R32G32B32A32_FLOAT,
quarter(width * 3)?,
height,
16,
GL_RGBA,
),
(Rgba, DType::F16) => lay(DXGI_FORMAT_R16G16B16A16_FLOAT, width, height, 8, GL_RGBA),
(Rgba, DType::F32) => lay(DXGI_FORMAT_R32G32B32A32_FLOAT, width, height, 16, GL_RGBA),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn packed_8bit_rows_are_direct() {
let l = image_d3d11_layout(PixelFormat::Rgba, DType::U8, 640, 480).unwrap();
assert_eq!(
(
l.dxgi_format,
l.texture_width,
l.texture_height,
l.bytes_per_texel,
l.gl_internal_format
),
(DXGI_FORMAT_R8G8B8A8_UNORM, 640, 480, 4, GL_RGBA)
);
let l = image_d3d11_layout(PixelFormat::Bgra, DType::U8, 640, 480).unwrap();
assert_eq!(
(l.dxgi_format, l.gl_internal_format),
(DXGI_FORMAT_B8G8R8A8_UNORM, GL_BGRA_EXT)
);
let l = image_d3d11_layout(PixelFormat::Grey, DType::U8, 641, 3).unwrap();
assert_eq!(
(
l.dxgi_format,
l.texture_width,
l.bytes_per_texel,
l.gl_internal_format
),
(DXGI_FORMAT_R8_UNORM, 641, 1, GL_RED_EXT)
);
assert_eq!(l.tight_bytes(), 641 * 3);
}
#[test]
fn packed_rgb_u8_packs_three_bytes_per_rgba_texel() {
let l = image_d3d11_layout(PixelFormat::Rgb, DType::U8, 640, 480).unwrap();
assert_eq!(
(
l.dxgi_format,
l.texture_width,
l.texture_height,
l.bytes_per_texel
),
(DXGI_FORMAT_R8G8B8A8_UNORM, 480, 480, 4)
);
assert!(
image_d3d11_layout(PixelFormat::Rgb, DType::U8, 641, 480).is_none(),
"W % 4 != 0 has no packing"
);
}
#[test]
fn semi_planar_is_the_combined_r8_plane() {
let l = image_d3d11_layout(PixelFormat::Nv12, DType::U8, 128, 64).unwrap();
assert_eq!(
(
l.dxgi_format,
l.texture_width,
l.texture_height,
l.gl_internal_format
),
(DXGI_FORMAT_R8_UNORM, 128, 96, GL_RED_EXT)
);
let l = image_d3d11_layout(PixelFormat::Nv16, DType::U8, 128, 64).unwrap();
assert_eq!((l.texture_width, l.texture_height), (128, 128));
let l = image_d3d11_layout(PixelFormat::Nv24, DType::U8, 128, 64).unwrap();
assert_eq!((l.texture_width, l.texture_height), (128, 192));
assert_eq!(
l.tight_bytes(),
PixelFormat::Nv24
.allocation_shape(128, 64)
.unwrap()
.iter()
.product::<usize>()
);
}
#[test]
fn semi_planar_rounds_an_odd_width_up_to_an_even_texture_row() {
for (fmt, rows) in [
(PixelFormat::Nv12, 362),
(PixelFormat::Nv16, 482),
(PixelFormat::Nv24, 723),
] {
let l = image_d3d11_layout(fmt, DType::U8, 321, 241).unwrap();
assert_eq!(
(l.texture_width, l.texture_height),
(322, rows),
"{fmt:?} 321x241"
);
}
let l = image_d3d11_layout(PixelFormat::Nv12, DType::U8, 640, 480).unwrap();
assert_eq!((l.texture_width, l.texture_height), (640, 720));
}
#[test]
fn widened_to_widens_and_never_narrows() {
let l = image_d3d11_layout(PixelFormat::Nv12, DType::U8, 321, 241).unwrap();
assert_eq!(l.texture_width, 322, "an odd width already rounds up");
assert_eq!(l.widened_to(64).texture_width, 322);
assert_eq!(l.widened_to(321).texture_width, 322);
assert_eq!(l.widened_to(322).texture_width, 322);
assert_eq!(l.widened_to(384).texture_width, 384);
assert_eq!(
l.widened_to(384).tight_row_bytes(),
384,
"R8 rows are texels"
);
let w = l.widened_to(384);
assert_eq!(
(
w.dxgi_format,
w.texture_height,
w.bytes_per_texel,
w.gl_internal_format
),
(
l.dxgi_format,
l.texture_height,
l.bytes_per_texel,
l.gl_internal_format
)
);
}
#[test]
fn yuyv_is_rg8_at_full_width() {
let l = image_d3d11_layout(PixelFormat::Yuyv, DType::U8, 320, 240).unwrap();
assert_eq!(
(
l.dxgi_format,
l.texture_width,
l.bytes_per_texel,
l.gl_internal_format
),
(DXGI_FORMAT_R8G8_UNORM, 320, 2, GL_RG_EXT)
);
}
#[test]
fn planar_float_packs_four_per_texel() {
let l = image_d3d11_layout(PixelFormat::PlanarRgb, DType::F16, 640, 480).unwrap();
assert_eq!(
(
l.dxgi_format,
l.texture_width,
l.texture_height,
l.bytes_per_texel
),
(DXGI_FORMAT_R16G16B16A16_FLOAT, 160, 1440, 8)
);
let l = image_d3d11_layout(PixelFormat::PlanarRgba, DType::F32, 640, 480).unwrap();
assert_eq!(
(
l.dxgi_format,
l.texture_width,
l.texture_height,
l.bytes_per_texel
),
(DXGI_FORMAT_R32G32B32A32_FLOAT, 160, 1920, 16)
);
assert_eq!(l.tight_bytes(), 4 * 480 * 640 * 4);
assert!(image_d3d11_layout(PixelFormat::PlanarRgb, DType::F16, 642, 480).is_none());
}
#[test]
fn interleaved_float_rows() {
let l = image_d3d11_layout(PixelFormat::Rgb, DType::F32, 640, 480).unwrap();
assert_eq!(
(
l.dxgi_format,
l.texture_width,
l.texture_height,
l.bytes_per_texel
),
(DXGI_FORMAT_R32G32B32A32_FLOAT, 480, 480, 16)
);
let l = image_d3d11_layout(PixelFormat::Rgba, DType::F16, 640, 480).unwrap();
assert_eq!(
(
l.dxgi_format,
l.texture_width,
l.texture_height,
l.bytes_per_texel
),
(DXGI_FORMAT_R16G16B16A16_FLOAT, 640, 480, 8)
);
}
#[test]
fn interleaved_float_rgb_f16_packs_three_halves_per_rgba_texel() {
assert_eq!(
image_d3d11_layout(PixelFormat::Rgb, DType::F16, 640, 480),
Some(D3d11ImageLayout {
dxgi_format: DXGI_FORMAT_R16G16B16A16_FLOAT,
texture_width: 480,
texture_height: 480,
bytes_per_texel: 8,
gl_internal_format: GL_RGBA,
})
);
assert!(image_d3d11_layout(PixelFormat::Rgb, DType::F16, 641, 480).is_none());
}
#[test]
fn interleaved_float_rgba_f32_is_one_pixel_per_texel() {
assert_eq!(
image_d3d11_layout(PixelFormat::Rgba, DType::F32, 640, 480),
Some(D3d11ImageLayout {
dxgi_format: DXGI_FORMAT_R32G32B32A32_FLOAT,
texture_width: 640,
texture_height: 480,
bytes_per_texel: 16,
gl_internal_format: GL_RGBA,
})
);
}
#[test]
fn a_zero_dimension_has_no_layout_for_any_supported_pair() {
let supported = [
(PixelFormat::Rgba, DType::U8),
(PixelFormat::Bgra, DType::U8),
(PixelFormat::Rgb, DType::U8),
(PixelFormat::Grey, DType::U8),
(PixelFormat::Nv12, DType::U8),
(PixelFormat::Nv16, DType::U8),
(PixelFormat::Nv24, DType::U8),
(PixelFormat::Yuyv, DType::U8),
(PixelFormat::PlanarRgb, DType::F16),
(PixelFormat::PlanarRgba, DType::F32),
(PixelFormat::Rgb, DType::F16),
(PixelFormat::Rgb, DType::F32),
(PixelFormat::Rgba, DType::F16),
(PixelFormat::Rgba, DType::F32),
];
for (fmt, dtype) in supported {
assert!(
image_d3d11_layout(fmt, dtype, 64, 64).is_some(),
"{fmt:?}/{dtype:?} 64x64 is supported"
);
for (w, h) in [(0, 64), (64, 0), (0, 0)] {
assert!(
image_d3d11_layout(fmt, dtype, w, h).is_none(),
"{fmt:?}/{dtype:?} {w}x{h}"
);
}
}
}
#[test]
fn unsupported_pairs_are_none() {
assert!(image_d3d11_layout(PixelFormat::Vyuy, DType::U8, 64, 64).is_none());
assert!(image_d3d11_layout(PixelFormat::Grey, DType::F16, 64, 64).is_none());
assert!(image_d3d11_layout(PixelFormat::Rgba, DType::U8, 0, 64).is_none());
}
}