use std::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::{
Direct3D11::{ID3D11Device, ID3D11Texture2D},
Dxgi::Common::{DXGI_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_NV12},
},
core::Interface,
};
use crate::{
buffer::MediaBuffer,
control::ControlMsg,
element::{Element, ElementType, Sink, element_pp_log},
elements::{SubmitError, filter::decoder::d3d11va_decoder::d3d11va_texture},
error::Result,
pool::UnboundObjectPoolRef,
};
pub trait D3d11FrameRenderer: Send {
fn device(&self) -> ID3D11Device;
unsafe fn submit_bgra_texture(
&self,
texture: ID3D11Texture2D,
array_index: u32,
width: u32,
height: u32,
) -> std::result::Result<(), SubmitError>;
unsafe fn submit_nv12_texture(
&self,
texture: ID3D11Texture2D,
array_index: u32,
width: u32,
height: u32,
) -> std::result::Result<(), SubmitError>;
fn resize(&self, width: u32, height: u32) -> std::result::Result<(), SubmitError>;
}
#[derive(Debug, ThisError)]
pub enum D3d11RendererError {
#[error("failed to submit frame: {0:?}")]
Submit(SubmitError),
#[error("failed to resize: {0:?}")]
Resize(SubmitError),
#[error("D3d11Renderer only handles Pixel::D3D11 frames, got {0:?}")]
UnsupportedFormat(ffmpeg::format::Pixel),
#[error(
"frame claimed the D3D11 pixel format but carries no texture — must \
come from D3d11Upload/D3d11Decoder/DxgiCaptureSource's GPU mode"
)]
InvalidD3d11Frame,
#[error(
"D3d11Renderer only draws DXGI_FORMAT_B8G8R8A8_UNORM or DXGI_FORMAT_NV12 textures, got {0:?}"
)]
UnsupportedTextureFormat(DXGI_FORMAT),
#[error(
"a Pixel::D3D11 frame's texture lives on a different ID3D11Device \
than this D3d11Renderer was created with — every D3D11 element in \
one pipeline must share exactly one device for zero-copy to be \
valid"
)]
DeviceMismatch,
#[error("D3D11 texture array index {index} is outside ArraySize {array_size}")]
InvalidArrayIndex { index: isize, array_size: u32 },
#[error("windows error: {0}")]
Windows(#[from] windows::core::Error),
}
pub struct D3d11Renderer {
pp_log: PpLog,
name: Arc<str>,
inner: Box<dyn D3d11FrameRenderer>,
device: ID3D11Device,
}
impl D3d11Renderer {
pub fn new(name: impl Into<String>, renderer: Box<dyn D3d11FrameRenderer>) -> Self {
let name: Arc<str> = name.into().into();
let pp_log = element_pp_log(ElementType::D3d11Renderer, &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(D3d11RendererError::Resize)?;
pp_info!(self, "resized: {width}x{height}");
Ok(())
}
fn submit_d3d11_frame(
&self,
frame: Arc<UnboundObjectPoolRef<ffmpeg::frame::Video>>,
) -> Result<()> {
let (texture_raw, index) =
d3d11va_texture(&frame).ok_or(D3d11RendererError::InvalidD3d11Frame)?;
let width = frame.width();
let height = frame.height();
let texture = unsafe {
ID3D11Texture2D::from_raw_borrowed(&texture_raw)
.expect("D3d11 frame's texture pointer must not be null")
.clone()
};
let texture_device = unsafe { texture.GetDevice() }.map_err(D3d11RendererError::from)?;
if texture_device.as_raw() != self.device.as_raw() {
return Err(D3d11RendererError::DeviceMismatch.into());
}
let mut desc = Default::default();
unsafe { texture.GetDesc(&mut desc) };
if index < 0 || index as u64 >= u64::from(desc.ArraySize) {
let error = D3d11RendererError::InvalidArrayIndex {
index,
array_size: desc.ArraySize,
};
pp_error!(self, "{error}");
return Err(error.into());
}
let array_index = index as u32;
match desc.Format {
DXGI_FORMAT_B8G8R8A8_UNORM => unsafe {
self.inner
.submit_bgra_texture(texture, array_index, width, height)
.map_err(D3d11RendererError::Submit)?;
},
DXGI_FORMAT_NV12 => unsafe {
self.inner
.submit_nv12_texture(texture, array_index, width, height)
.map_err(D3d11RendererError::Submit)?;
},
other => return Err(D3d11RendererError::UnsupportedTextureFormat(other).into()),
}
Ok(())
}
}
impl Element for D3d11Renderer {
fn name(&self) -> Arc<str> {
self.name.clone()
}
fn element_type(&self) -> ElementType {
ElementType::D3d11Renderer
}
fn pp_log(&self) -> &PpLog {
&self.pp_log
}
fn pp_log_mut(&mut self) -> &mut PpLog {
&mut self.pp_log
}
}
impl Sink for D3d11Renderer {
fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
let MediaBuffer::Video(frame) = buf else {
return Ok(());
};
if frame.format() != ffmpeg::format::Pixel::D3D11 {
let format = frame.format();
pp_error!(self, "unsupported pixel format: {format:?}");
return Err(D3d11RendererError::UnsupportedFormat(format).into());
}
self.submit_d3d11_frame(frame)
.inspect_err(|error| pp_error!(self, "submit_d3d11_frame failed: {error}"))
}
fn control(&mut self, _msg: ControlMsg) -> Result<()> {
Ok(())
}
}