diff --git a/lightningbeam-ui/gpu-video-encoder/src/decoder.rs b/lightningbeam-ui/gpu-video-encoder/src/decoder.rs index 9f64856..884c66f 100644 --- a/lightningbeam-ui/gpu-video-encoder/src/decoder.rs +++ b/lightningbeam-ui/gpu-video-encoder/src/decoder.rs @@ -194,6 +194,7 @@ impl VaapiDecoder { y_pitch: y_pl.pitch as u64, uv_offset: uv_pl.offset as u64, uv_pitch: uv_pl.pitch as u64, + ten_bit: false, }; 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 diff --git a/lightningbeam-ui/gpu-video-encoder/src/dmabuf.rs b/lightningbeam-ui/gpu-video-encoder/src/dmabuf.rs index 492e0b4..7cc72b4 100644 --- a/lightningbeam-ui/gpu-video-encoder/src/dmabuf.rs +++ b/lightningbeam-ui/gpu-video-encoder/src/dmabuf.rs @@ -6,7 +6,7 @@ use crate::vaapi::MappedSurface; use crate::vk_device::DrmDevice; 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)] pub struct Nv12DmaBuf { pub fd: i32, @@ -18,6 +18,9 @@ pub struct Nv12DmaBuf { pub y_pitch: u64, pub uv_offset: 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 @@ -68,6 +71,7 @@ pub fn import(drm: &DrmDevice, surf: &MappedSurface) -> Result i, diff --git a/lightningbeam-ui/gpu-video-encoder/src/vk_device.rs b/lightningbeam-ui/gpu-video-encoder/src/vk_device.rs index 93a67aa..fcff428 100644 --- a/lightningbeam-ui/gpu-video-encoder/src/vk_device.rs +++ b/lightningbeam-ui/gpu-video-encoder/src/vk_device.rs @@ -143,7 +143,10 @@ unsafe fn create_inner(windowed: bool) -> Result { open_device, &wgpu::DeviceDescriptor { 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. // 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. diff --git a/lightningbeam-ui/lightningbeam-editor/src/hw_video.rs b/lightningbeam-ui/lightningbeam-editor/src/hw_video.rs index d3f915d..f3226d6 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/hw_video.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/hw_video.rs @@ -36,6 +36,26 @@ impl HwVideoImporter for SharedHwImporter { let obj = &(*desc).objects[0]; let width = (*frame).width 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. let (y_pl, uv_pl) = if (*desc).nb_layers >= 2 { (&(*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, uv_offset: uv_pl.offset as u64, uv_pitch: uv_pl.pitch as u64, + ten_bit, }; let full_range = (*frame).color_range == ff::AVColorRange::AVCOL_RANGE_JPEG;