1use crate::{DType, PixelFormat};
12
13pub const DXGI_FORMAT_R32G32B32A32_FLOAT: u32 = 2;
14pub const DXGI_FORMAT_R16G16B16A16_FLOAT: u32 = 10;
15pub const DXGI_FORMAT_R8G8B8A8_UNORM: u32 = 28;
16pub const DXGI_FORMAT_R8G8_UNORM: u32 = 49;
17pub const DXGI_FORMAT_R8_UNORM: u32 = 61;
18pub const DXGI_FORMAT_B8G8R8A8_UNORM: u32 = 87;
19
20pub const GL_RGBA: u32 = 0x1908;
23pub const GL_BGRA_EXT: u32 = 0x80E1;
24pub const GL_RED_EXT: u32 = 0x1903;
25pub const GL_RG_EXT: u32 = 0x8227;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct D3d11ImageLayout {
33 pub dxgi_format: u32,
34 pub texture_width: usize,
35 pub texture_height: usize,
36 pub bytes_per_texel: usize,
37 pub gl_internal_format: u32,
38}
39
40impl D3d11ImageLayout {
41 pub fn tight_row_bytes(&self) -> usize {
50 self.texture_width * self.bytes_per_texel
51 }
52 pub fn tight_bytes(&self) -> usize {
55 self.tight_row_bytes() * self.texture_height
56 }
57
58 pub fn widened_to(&self, texture_width: usize) -> Self {
70 Self {
71 texture_width: texture_width.max(self.texture_width),
72 ..*self
73 }
74 }
75}
76
77fn lay(dxgi_format: u32, w: usize, h: usize, bpt: usize, gl: u32) -> Option<D3d11ImageLayout> {
81 Some(D3d11ImageLayout {
82 dxgi_format,
83 texture_width: w,
84 texture_height: h,
85 bytes_per_texel: bpt,
86 gl_internal_format: gl,
87 })
88}
89
90pub fn image_d3d11_layout(
94 format: PixelFormat,
95 dtype: DType,
96 width: usize,
97 height: usize,
98) -> Option<D3d11ImageLayout> {
99 use PixelFormat::*;
100 if width == 0 || height == 0 {
101 return None;
102 }
103 let quarter = |n: usize| {
106 if n.is_multiple_of(4) {
107 Some(n / 4)
108 } else {
109 None
110 }
111 };
112 match (format, dtype) {
113 (Rgba, DType::U8 | DType::I8) => lay(DXGI_FORMAT_R8G8B8A8_UNORM, width, height, 4, GL_RGBA),
114 (Bgra, DType::U8) => lay(DXGI_FORMAT_B8G8R8A8_UNORM, width, height, 4, GL_BGRA_EXT),
115 (Rgb, DType::U8 | DType::I8) => lay(
116 DXGI_FORMAT_R8G8B8A8_UNORM,
117 quarter(width * 3)?,
118 height,
119 4,
120 GL_RGBA,
121 ),
122 (Grey, DType::U8) => lay(DXGI_FORMAT_R8_UNORM, width, height, 1, GL_RED_EXT),
123 (Nv12 | Nv16 | Nv24, DType::U8) => {
135 let shape = format.allocation_shape(width, height)?;
136 lay(
137 DXGI_FORMAT_R8_UNORM,
138 shape[1].next_multiple_of(2),
139 shape[0],
140 1,
141 GL_RED_EXT,
142 )
143 }
144 (Yuyv, DType::U8) => lay(DXGI_FORMAT_R8G8_UNORM, width, height, 2, GL_RG_EXT),
145 (PlanarRgb | PlanarRgba, DType::F16) => lay(
146 DXGI_FORMAT_R16G16B16A16_FLOAT,
147 quarter(width)?,
148 format.channels() * height,
149 8,
150 GL_RGBA,
151 ),
152 (PlanarRgb | PlanarRgba, DType::F32) => lay(
153 DXGI_FORMAT_R32G32B32A32_FLOAT,
154 quarter(width)?,
155 format.channels() * height,
156 16,
157 GL_RGBA,
158 ),
159 (Rgb, DType::F16) => lay(
160 DXGI_FORMAT_R16G16B16A16_FLOAT,
161 quarter(width * 3)?,
162 height,
163 8,
164 GL_RGBA,
165 ),
166 (Rgb, DType::F32) => lay(
167 DXGI_FORMAT_R32G32B32A32_FLOAT,
168 quarter(width * 3)?,
169 height,
170 16,
171 GL_RGBA,
172 ),
173 (Rgba, DType::F16) => lay(DXGI_FORMAT_R16G16B16A16_FLOAT, width, height, 8, GL_RGBA),
174 (Rgba, DType::F32) => lay(DXGI_FORMAT_R32G32B32A32_FLOAT, width, height, 16, GL_RGBA),
175 _ => None,
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn packed_8bit_rows_are_direct() {
185 let l = image_d3d11_layout(PixelFormat::Rgba, DType::U8, 640, 480).unwrap();
186 assert_eq!(
187 (
188 l.dxgi_format,
189 l.texture_width,
190 l.texture_height,
191 l.bytes_per_texel,
192 l.gl_internal_format
193 ),
194 (DXGI_FORMAT_R8G8B8A8_UNORM, 640, 480, 4, GL_RGBA)
195 );
196 let l = image_d3d11_layout(PixelFormat::Bgra, DType::U8, 640, 480).unwrap();
197 assert_eq!(
198 (l.dxgi_format, l.gl_internal_format),
199 (DXGI_FORMAT_B8G8R8A8_UNORM, GL_BGRA_EXT)
200 );
201 let l = image_d3d11_layout(PixelFormat::Grey, DType::U8, 641, 3).unwrap();
202 assert_eq!(
203 (
204 l.dxgi_format,
205 l.texture_width,
206 l.bytes_per_texel,
207 l.gl_internal_format
208 ),
209 (DXGI_FORMAT_R8_UNORM, 641, 1, GL_RED_EXT)
210 );
211 assert_eq!(l.tight_bytes(), 641 * 3);
212 }
213
214 #[test]
215 fn packed_rgb_u8_packs_three_bytes_per_rgba_texel() {
216 let l = image_d3d11_layout(PixelFormat::Rgb, DType::U8, 640, 480).unwrap();
217 assert_eq!(
218 (
219 l.dxgi_format,
220 l.texture_width,
221 l.texture_height,
222 l.bytes_per_texel
223 ),
224 (DXGI_FORMAT_R8G8B8A8_UNORM, 480, 480, 4)
225 );
226 assert!(
227 image_d3d11_layout(PixelFormat::Rgb, DType::U8, 641, 480).is_none(),
228 "W % 4 != 0 has no packing"
229 );
230 }
231
232 #[test]
233 fn semi_planar_is_the_combined_r8_plane() {
234 let l = image_d3d11_layout(PixelFormat::Nv12, DType::U8, 128, 64).unwrap();
235 assert_eq!(
236 (
237 l.dxgi_format,
238 l.texture_width,
239 l.texture_height,
240 l.gl_internal_format
241 ),
242 (DXGI_FORMAT_R8_UNORM, 128, 96, GL_RED_EXT)
243 );
244 let l = image_d3d11_layout(PixelFormat::Nv16, DType::U8, 128, 64).unwrap();
245 assert_eq!((l.texture_width, l.texture_height), (128, 128));
246 let l = image_d3d11_layout(PixelFormat::Nv24, DType::U8, 128, 64).unwrap();
247 assert_eq!((l.texture_width, l.texture_height), (128, 192));
248 assert_eq!(
249 l.tight_bytes(),
250 PixelFormat::Nv24
251 .allocation_shape(128, 64)
252 .unwrap()
253 .iter()
254 .product::<usize>()
255 );
256 }
257
258 #[test]
262 fn semi_planar_rounds_an_odd_width_up_to_an_even_texture_row() {
263 for (fmt, rows) in [
264 (PixelFormat::Nv12, 362),
265 (PixelFormat::Nv16, 482),
266 (PixelFormat::Nv24, 723),
267 ] {
268 let l = image_d3d11_layout(fmt, DType::U8, 321, 241).unwrap();
269 assert_eq!(
270 (l.texture_width, l.texture_height),
271 (322, rows),
272 "{fmt:?} 321x241"
273 );
274 }
275 let l = image_d3d11_layout(PixelFormat::Nv12, DType::U8, 640, 480).unwrap();
277 assert_eq!((l.texture_width, l.texture_height), (640, 720));
278 }
279
280 #[test]
285 fn widened_to_widens_and_never_narrows() {
286 let l = image_d3d11_layout(PixelFormat::Nv12, DType::U8, 321, 241).unwrap();
287 assert_eq!(l.texture_width, 322, "an odd width already rounds up");
288
289 assert_eq!(l.widened_to(64).texture_width, 322);
291 assert_eq!(l.widened_to(321).texture_width, 322);
292 assert_eq!(l.widened_to(322).texture_width, 322);
294 assert_eq!(l.widened_to(384).texture_width, 384);
295 assert_eq!(
296 l.widened_to(384).tight_row_bytes(),
297 384,
298 "R8 rows are texels"
299 );
300 let w = l.widened_to(384);
302 assert_eq!(
303 (
304 w.dxgi_format,
305 w.texture_height,
306 w.bytes_per_texel,
307 w.gl_internal_format
308 ),
309 (
310 l.dxgi_format,
311 l.texture_height,
312 l.bytes_per_texel,
313 l.gl_internal_format
314 )
315 );
316 }
317
318 #[test]
319 fn yuyv_is_rg8_at_full_width() {
320 let l = image_d3d11_layout(PixelFormat::Yuyv, DType::U8, 320, 240).unwrap();
321 assert_eq!(
322 (
323 l.dxgi_format,
324 l.texture_width,
325 l.bytes_per_texel,
326 l.gl_internal_format
327 ),
328 (DXGI_FORMAT_R8G8_UNORM, 320, 2, GL_RG_EXT)
329 );
330 }
331
332 #[test]
333 fn planar_float_packs_four_per_texel() {
334 let l = image_d3d11_layout(PixelFormat::PlanarRgb, DType::F16, 640, 480).unwrap();
335 assert_eq!(
336 (
337 l.dxgi_format,
338 l.texture_width,
339 l.texture_height,
340 l.bytes_per_texel
341 ),
342 (DXGI_FORMAT_R16G16B16A16_FLOAT, 160, 1440, 8)
343 );
344 let l = image_d3d11_layout(PixelFormat::PlanarRgba, DType::F32, 640, 480).unwrap();
345 assert_eq!(
346 (
347 l.dxgi_format,
348 l.texture_width,
349 l.texture_height,
350 l.bytes_per_texel
351 ),
352 (DXGI_FORMAT_R32G32B32A32_FLOAT, 160, 1920, 16)
353 );
354 assert_eq!(l.tight_bytes(), 4 * 480 * 640 * 4);
355 assert!(image_d3d11_layout(PixelFormat::PlanarRgb, DType::F16, 642, 480).is_none());
356 }
357
358 #[test]
359 fn interleaved_float_rows() {
360 let l = image_d3d11_layout(PixelFormat::Rgb, DType::F32, 640, 480).unwrap();
361 assert_eq!(
362 (
363 l.dxgi_format,
364 l.texture_width,
365 l.texture_height,
366 l.bytes_per_texel
367 ),
368 (DXGI_FORMAT_R32G32B32A32_FLOAT, 480, 480, 16)
369 );
370 let l = image_d3d11_layout(PixelFormat::Rgba, DType::F16, 640, 480).unwrap();
371 assert_eq!(
372 (
373 l.dxgi_format,
374 l.texture_width,
375 l.texture_height,
376 l.bytes_per_texel
377 ),
378 (DXGI_FORMAT_R16G16B16A16_FLOAT, 640, 480, 8)
379 );
380 }
381
382 #[test]
383 fn interleaved_float_rgb_f16_packs_three_halves_per_rgba_texel() {
384 assert_eq!(
385 image_d3d11_layout(PixelFormat::Rgb, DType::F16, 640, 480),
386 Some(D3d11ImageLayout {
387 dxgi_format: DXGI_FORMAT_R16G16B16A16_FLOAT,
388 texture_width: 480,
389 texture_height: 480,
390 bytes_per_texel: 8,
391 gl_internal_format: GL_RGBA,
392 })
393 );
394 assert!(image_d3d11_layout(PixelFormat::Rgb, DType::F16, 641, 480).is_none());
395 }
396
397 #[test]
398 fn interleaved_float_rgba_f32_is_one_pixel_per_texel() {
399 assert_eq!(
400 image_d3d11_layout(PixelFormat::Rgba, DType::F32, 640, 480),
401 Some(D3d11ImageLayout {
402 dxgi_format: DXGI_FORMAT_R32G32B32A32_FLOAT,
403 texture_width: 640,
404 texture_height: 480,
405 bytes_per_texel: 16,
406 gl_internal_format: GL_RGBA,
407 })
408 );
409 }
410
411 #[test]
412 fn a_zero_dimension_has_no_layout_for_any_supported_pair() {
413 let supported = [
414 (PixelFormat::Rgba, DType::U8),
415 (PixelFormat::Bgra, DType::U8),
416 (PixelFormat::Rgb, DType::U8),
417 (PixelFormat::Grey, DType::U8),
418 (PixelFormat::Nv12, DType::U8),
419 (PixelFormat::Nv16, DType::U8),
420 (PixelFormat::Nv24, DType::U8),
421 (PixelFormat::Yuyv, DType::U8),
422 (PixelFormat::PlanarRgb, DType::F16),
423 (PixelFormat::PlanarRgba, DType::F32),
424 (PixelFormat::Rgb, DType::F16),
425 (PixelFormat::Rgb, DType::F32),
426 (PixelFormat::Rgba, DType::F16),
427 (PixelFormat::Rgba, DType::F32),
428 ];
429 for (fmt, dtype) in supported {
430 assert!(
431 image_d3d11_layout(fmt, dtype, 64, 64).is_some(),
432 "{fmt:?}/{dtype:?} 64x64 is supported"
433 );
434 for (w, h) in [(0, 64), (64, 0), (0, 0)] {
435 assert!(
436 image_d3d11_layout(fmt, dtype, w, h).is_none(),
437 "{fmt:?}/{dtype:?} {w}x{h}"
438 );
439 }
440 }
441 }
442
443 #[test]
444 fn unsupported_pairs_are_none() {
445 assert!(image_d3d11_layout(PixelFormat::Vyuy, DType::U8, 64, 64).is_none());
446 assert!(image_d3d11_layout(PixelFormat::Grey, DType::F16, 64, 64).is_none());
447 assert!(image_d3d11_layout(PixelFormat::Rgba, DType::U8, 0, 64).is_none());
448 }
449}