use std::{any::Any, sync::Arc};
use crate::pp_log::{PpLog, pp_error, pp_info};
use ffmpeg_next as ffmpeg;
use thiserror::Error as ThisError;
use windows::{
Win32::Graphics::Direct3D12::{ID3D12Device, ID3D12Fence, ID3D12Resource},
core::Interface,
};
use crate::{
buffer::MediaBuffer,
control::ControlMsg,
element::{Element, ElementType, Sink, element_pp_log},
elements::{SubmitError, filter::decoder::d3d12va_decoder::d3d12va_texture},
error::Result,
pool::UnboundObjectPoolRef,
};
#[derive(Clone, Copy)]
pub struct RawPlane {
pub data: *const u8,
pub len: usize,
pub stride: usize,
}
pub trait D3d12FrameRenderer: Send {
fn device(&self) -> ID3D12Device;
unsafe fn submit_yuv420p(
&self,
y: RawPlane,
u: RawPlane,
v: RawPlane,
width: u32,
height: u32,
) -> std::result::Result<(), SubmitError>;
unsafe fn submit_nv12_texture(
&self,
texture: ID3D12Resource,
fence: ID3D12Fence,
fence_value: u64,
width: u32,
height: u32,
keep_alive: Box<dyn Any + Send>,
) -> std::result::Result<(), SubmitError>;
fn resize(&self, width: u32, height: u32) -> std::result::Result<(), SubmitError>;
}
#[derive(Debug, ThisError)]
pub enum D3d12RendererError {
#[error("failed to submit frame: {0:?}")]
Submit(SubmitError),
#[error("failed to resize: {0:?}")]
Resize(SubmitError),
#[error(
"D3d12Renderer only handles YUV420P frames (CPU) or D3D12 frames \
(from D3d12vaDecoder), got {0:?}"
)]
UnsupportedFormat(ffmpeg::format::Pixel),
#[error(
"frame claimed the D3D12 pixel format but has no AVD3D12VAFrame \
payload — must come from D3d12vaDecoder"
)]
InvalidD3d12Frame,
#[error(
"a Pixel::D3D12 frame's texture lives on a different ID3D12Device \
than this D3d12Renderer was created with — the producer \
(D3d12vaDecoder/D3d12Upload) and the D3d12FrameRenderer impl \
must share the same device for zero-copy to be valid"
)]
DeviceMismatch,
}
pub struct D3d12Renderer {
pp_log: PpLog,
name: Arc<str>,
inner: Box<dyn D3d12FrameRenderer>,
device: ID3D12Device,
}
impl D3d12Renderer {
pub fn new(name: impl Into<String>, renderer: Box<dyn D3d12FrameRenderer>) -> Self {
let name: Arc<str> = name.into().into();
let pp_log = element_pp_log(ElementType::D3d12Renderer, &name, None);
pp_info!(pp_log: &pp_log, "created");
let device = renderer.device();
Self {
name,
pp_log,
inner: renderer,
device,
}
}
pub fn resize(&self, width: u32, height: u32) -> Result<()> {
self.inner
.resize(width, height)
.inspect_err(|error| pp_error!(self, "resize failed: {error:?}"))
.map_err(D3d12RendererError::Resize)?;
pp_info!(self, "resized: {width}x{height}");
Ok(())
}
fn submit_yuv420p_frame(&self, frame: &ffmpeg::frame::Video) -> Result<()> {
let plane = |index: usize| RawPlane {
data: frame.data(index).as_ptr(),
len: frame.data(index).len(),
stride: frame.stride(index),
};
unsafe {
self.inner
.submit_yuv420p(plane(0), plane(1), plane(2), frame.width(), frame.height())
.map_err(D3d12RendererError::Submit)?;
}
Ok(())
}
fn submit_d3d12_frame(
&self,
frame: Arc<UnboundObjectPoolRef<ffmpeg::frame::Video>>,
) -> Result<()> {
let (texture_raw, fence_raw, fence_value) =
d3d12va_texture(&frame).ok_or(D3d12RendererError::InvalidD3d12Frame)?;
let width = frame.width();
let height = frame.height();
let (texture, fence) = unsafe {
let texture = ID3D12Resource::from_raw_borrowed(&texture_raw)
.expect("AVD3D12VAFrame.texture must not be null")
.clone();
let fence = ID3D12Fence::from_raw_borrowed(&fence_raw)
.expect("AVD3D12VAFrame.sync_ctx.fence must not be null")
.clone();
(texture, fence)
};
let mut texture_device: Option<ID3D12Device> = None;
unsafe { texture.GetDevice(&mut texture_device) }
.map_err(|_| D3d12RendererError::DeviceMismatch)?;
let texture_device = texture_device.ok_or(D3d12RendererError::DeviceMismatch)?;
if texture_device.as_raw() != self.device.as_raw() {
return Err(D3d12RendererError::DeviceMismatch.into());
}
unsafe {
self.inner
.submit_nv12_texture(texture, fence, fence_value, width, height, Box::new(frame))
.map_err(D3d12RendererError::Submit)?;
}
Ok(())
}
}
impl Element for D3d12Renderer {
fn name(&self) -> Arc<str> {
self.name.clone()
}
fn element_type(&self) -> ElementType {
ElementType::D3d12Renderer
}
fn pp_log(&self) -> &PpLog {
&self.pp_log
}
fn pp_log_mut(&mut self) -> &mut PpLog {
&mut self.pp_log
}
}
impl Sink for D3d12Renderer {
fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
let MediaBuffer::Video(frame) = buf else {
return Ok(());
};
match frame.format() {
ffmpeg::format::Pixel::YUV420P => self
.submit_yuv420p_frame(&frame)
.inspect_err(|error| pp_error!(self, "submit_yuv420p_frame failed: {error}")),
ffmpeg::format::Pixel::D3D12 => self
.submit_d3d12_frame(frame)
.inspect_err(|error| pp_error!(self, "submit_d3d12_frame failed: {error}")),
other => {
pp_error!(self, "unsupported pixel format: {other:?}");
Err(D3d12RendererError::UnsupportedFormat(other).into())
}
}
}
fn control(&mut self, _msg: ControlMsg) -> Result<()> {
Ok(())
}
}