hw: P010 (10-bit) DMA-BUF import for real HDR video
10/12-bit HDR decodes to P010-style VAAPI surfaces (16-bit planes), which the 8-bit NV12 import couldn't handle — real HDR clips fell back to software (losing the HDR). Import them as R16/Rg16 plane textures: - dmabuf::Nv12DmaBuf gains `ten_bit`; import_raw picks R16_UNORM/R16G16_UNORM (vk) + R16Unorm/Rg16Unorm (wgpu) when set, else the existing R8/Rg8. - hw_video.rs detects 10-bit from the hw frames context sw_format (P010/P012/P016) and sets it. The NV12→RGB shader is unchanged: it samples normalized floats, and the limited/full de-quant lands at the same values regardless of bit depth. - vk_device requests TEXTURE_FORMAT_16BIT_NORM when the adapter supports it (R16/ Rg16 need it); absent → 10-bit falls back to software, 8-bit unaffected. Pairs with the PQ/HLG/BT.2020 colour math so HDR10/HLG clips now reach the GPU path end-to-end. NV12 callers (encoder, decode primitive) pass ten_bit=false. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b76eefb404
commit
6c8cb5b9e0
|
|
@ -194,6 +194,7 @@ impl VaapiDecoder {
|
||||||
y_pitch: y_pl.pitch as u64,
|
y_pitch: y_pl.pitch as u64,
|
||||||
uv_offset: uv_pl.offset as u64,
|
uv_offset: uv_pl.offset as u64,
|
||||||
uv_pitch: uv_pl.pitch as u64,
|
uv_pitch: uv_pl.pitch as u64,
|
||||||
|
ten_bit: false,
|
||||||
};
|
};
|
||||||
let imported = dmabuf::import_raw(&self.drm.device, &self.drm.adapter, &buf);
|
let imported = dmabuf::import_raw(&self.drm.device, &self.drm.adapter, &buf);
|
||||||
ff::av_frame_free(&mut (drm_f as *mut _)); // the fd was dup'd into Vulkan
|
ff::av_frame_free(&mut (drm_f as *mut _)); // the fd was dup'd into Vulkan
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use crate::vaapi::MappedSurface;
|
||||||
use crate::vk_device::DrmDevice;
|
use crate::vk_device::DrmDevice;
|
||||||
use ash::vk;
|
use ash::vk;
|
||||||
|
|
||||||
/// Plane layout for a single-object NV12 DMA-BUF (the common VAAPI case).
|
/// Plane layout for a single-object NV12/P010 DMA-BUF (the common VAAPI case).
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Nv12DmaBuf {
|
pub struct Nv12DmaBuf {
|
||||||
pub fd: i32,
|
pub fd: i32,
|
||||||
|
|
@ -18,6 +18,9 @@ pub struct Nv12DmaBuf {
|
||||||
pub y_pitch: u64,
|
pub y_pitch: u64,
|
||||||
pub uv_offset: u64,
|
pub uv_offset: u64,
|
||||||
pub uv_pitch: u64,
|
pub uv_pitch: u64,
|
||||||
|
/// True for 10/12/16-bit content (P010 etc.): planes are 16-bit (R16/Rg16) rather than 8-bit
|
||||||
|
/// (R8/Rg8). The sampled float is normalized either way, so the consumer needs no change.
|
||||||
|
pub ten_bit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Frees the shared imported `VkDeviceMemory` once both plane images are gone. Held by
|
/// Frees the shared imported `VkDeviceMemory` once both plane images are gone. Held by
|
||||||
|
|
@ -68,6 +71,7 @@ pub fn import(drm: &DrmDevice, surf: &MappedSurface) -> Result<ImportedNv12, Str
|
||||||
y_pitch: surf.y_pitch,
|
y_pitch: surf.y_pitch,
|
||||||
uv_offset: surf.uv_offset,
|
uv_offset: surf.uv_offset,
|
||||||
uv_pitch: surf.uv_pitch,
|
uv_pitch: surf.uv_pitch,
|
||||||
|
ten_bit: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -130,8 +134,21 @@ pub fn import_raw(
|
||||||
.map_err(|e| format!("vkCreateImage(modifier) failed: {e:?}"))
|
.map_err(|e| format!("vkCreateImage(modifier) failed: {e:?}"))
|
||||||
};
|
};
|
||||||
|
|
||||||
let img_y = make_image(vk::Format::R8_UNORM, buf.width, buf.height, buf.y_pitch)?;
|
// 8-bit NV12 → R8/Rg8 planes; 10/12/16-bit P010-style → R16/Rg16 (sampled value is
|
||||||
let img_uv = make_image(vk::Format::R8G8_UNORM, buf.width / 2, buf.height / 2, buf.uv_pitch)?;
|
// normalized either way, so the NV12→RGB consumer is unchanged).
|
||||||
|
let (vk_y, vk_uv) = if buf.ten_bit {
|
||||||
|
(vk::Format::R16_UNORM, vk::Format::R16G16_UNORM)
|
||||||
|
} else {
|
||||||
|
(vk::Format::R8_UNORM, vk::Format::R8G8_UNORM)
|
||||||
|
};
|
||||||
|
let (wgpu_y, wgpu_uv) = if buf.ten_bit {
|
||||||
|
(wgpu::TextureFormat::R16Unorm, wgpu::TextureFormat::Rg16Unorm)
|
||||||
|
} else {
|
||||||
|
(wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::Rg8Unorm)
|
||||||
|
};
|
||||||
|
|
||||||
|
let img_y = make_image(vk_y, buf.width, buf.height, buf.y_pitch)?;
|
||||||
|
let img_uv = make_image(vk_uv, buf.width / 2, buf.height / 2, buf.uv_pitch)?;
|
||||||
|
|
||||||
let fd_dev = ash::khr::external_memory_fd::Device::new(instance, &raw_device);
|
let fd_dev = ash::khr::external_memory_fd::Device::new(instance, &raw_device);
|
||||||
let mut fd_props = vk::MemoryFdPropertiesKHR::default();
|
let mut fd_props = vk::MemoryFdPropertiesKHR::default();
|
||||||
|
|
@ -206,8 +223,8 @@ pub fn import_raw(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let y = wrap(img_y, wgpu::TextureFormat::R8Unorm, buf.width, buf.height);
|
let y = wrap(img_y, wgpu_y, buf.width, buf.height);
|
||||||
let uv = wrap(img_uv, wgpu::TextureFormat::Rg8Unorm, buf.width / 2, buf.height / 2);
|
let uv = wrap(img_uv, wgpu_uv, buf.width / 2, buf.height / 2);
|
||||||
drop(hal_device);
|
drop(hal_device);
|
||||||
|
|
||||||
Ok(ImportedNv12 { y, uv })
|
Ok(ImportedNv12 { y, uv })
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,7 @@ impl ZeroCopyEncoder {
|
||||||
y_pitch: y.pitch as u64,
|
y_pitch: y.pitch as u64,
|
||||||
uv_offset: uv.offset as u64,
|
uv_offset: uv.offset as u64,
|
||||||
uv_pitch: uv.pitch as u64,
|
uv_pitch: uv.pitch as u64,
|
||||||
|
ten_bit: false,
|
||||||
};
|
};
|
||||||
let imported = match dmabuf::import_raw(&self.drm.device, &self.drm.adapter, &buf) {
|
let imported = match dmabuf::import_raw(&self.drm.device, &self.drm.adapter, &buf) {
|
||||||
Ok(i) => i,
|
Ok(i) => i,
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,10 @@ unsafe fn create_inner(windowed: bool) -> Result<DrmDevice, String> {
|
||||||
open_device,
|
open_device,
|
||||||
&wgpu::DeviceDescriptor {
|
&wgpu::DeviceDescriptor {
|
||||||
label: Some("drm-import-device"),
|
label: Some("drm-import-device"),
|
||||||
required_features: wgpu::Features::empty(),
|
// R16/Rg16 plane textures for P010 (10-bit HDR) import need this; request it only
|
||||||
|
// when the adapter supports it (else 10-bit falls back to software decode).
|
||||||
|
required_features: wgpu_adapter.features()
|
||||||
|
& wgpu::Features::TEXTURE_FORMAT_16BIT_NORM,
|
||||||
// Vello's compute pipelines need more than downlevel limits (e.g.
|
// Vello's compute pipelines need more than downlevel limits (e.g.
|
||||||
// max_storage_buffers_per_shader_stage >= 5). This device only ever runs on a
|
// max_storage_buffers_per_shader_stage >= 5). This device only ever runs on a
|
||||||
// real VAAPI-capable GPU, so request the adapter's full limits.
|
// real VAAPI-capable GPU, so request the adapter's full limits.
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,26 @@ impl HwVideoImporter for SharedHwImporter {
|
||||||
let obj = &(*desc).objects[0];
|
let obj = &(*desc).objects[0];
|
||||||
let width = (*frame).width as u32;
|
let width = (*frame).width as u32;
|
||||||
let height = (*frame).height as u32;
|
let height = (*frame).height as u32;
|
||||||
|
|
||||||
|
// 10/12/16-bit content decodes to P010-style surfaces (16-bit planes). Detect via the hw
|
||||||
|
// frames context's software format so the import builds R16/Rg16 textures.
|
||||||
|
let ten_bit = {
|
||||||
|
let hwfc = (*frame).hw_frames_ctx;
|
||||||
|
if hwfc.is_null() {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
let ctx = (*hwfc).data as *const ff::AVHWFramesContext;
|
||||||
|
matches!(
|
||||||
|
(*ctx).sw_format,
|
||||||
|
ff::AVPixelFormat::AV_PIX_FMT_P010LE
|
||||||
|
| ff::AVPixelFormat::AV_PIX_FMT_P010BE
|
||||||
|
| ff::AVPixelFormat::AV_PIX_FMT_P012LE
|
||||||
|
| ff::AVPixelFormat::AV_PIX_FMT_P012BE
|
||||||
|
| ff::AVPixelFormat::AV_PIX_FMT_P016LE
|
||||||
|
| ff::AVPixelFormat::AV_PIX_FMT_P016BE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
// NV12: Y then UV — two layers (one plane each) or one layer with two planes.
|
// NV12: Y then UV — two layers (one plane each) or one layer with two planes.
|
||||||
let (y_pl, uv_pl) = if (*desc).nb_layers >= 2 {
|
let (y_pl, uv_pl) = if (*desc).nb_layers >= 2 {
|
||||||
(&(*desc).layers[0].planes[0], &(*desc).layers[1].planes[0])
|
(&(*desc).layers[0].planes[0], &(*desc).layers[1].planes[0])
|
||||||
|
|
@ -52,6 +72,7 @@ impl HwVideoImporter for SharedHwImporter {
|
||||||
y_pitch: y_pl.pitch as u64,
|
y_pitch: y_pl.pitch as u64,
|
||||||
uv_offset: uv_pl.offset as u64,
|
uv_offset: uv_pl.offset as u64,
|
||||||
uv_pitch: uv_pl.pitch as u64,
|
uv_pitch: uv_pl.pitch as u64,
|
||||||
|
ten_bit,
|
||||||
};
|
};
|
||||||
let full_range = (*frame).color_range == ff::AVColorRange::AVCOL_RANGE_JPEG;
|
let full_range = (*frame).color_range == ff::AVColorRange::AVCOL_RANGE_JPEG;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue