use std::{
error::Error,
ffi::c_void,
fmt,
mem::ManuallyDrop,
ptr::NonNull,
sync::{
Arc, OnceLock,
atomic::{AtomicBool, Ordering},
},
};
use alpha2_ffmpeg_bridge::{BridgeError, D3D12FrameLease, create_d3d12_device_context};
use alpha2_gpu::GpuContext;
use rsmpeg::avutil::AVHWDeviceContext;
use windows::{
Win32::Graphics::Direct3D12::{ID3D12Fence, ID3D12Resource},
core::Interface,
};
use crate::{NativeGpuIdentity, NativePlaneLayout};
const DXGI_FORMAT_NV12: u32 = 103;
const DXGI_FORMAT_P010: u32 = 104;
const COPY_SUBMITTED_UNFENCED: i32 = crate::interop::ALPHA2_GPU_STEP_QUEUE_SIGNAL;
use crate::interop::Alpha2D3D12CopyInfo as D3D12CopyInfo;
use crate::interop::{
Alpha2D3D12EncodeCaps as D3D12EncodeCaps, Alpha2D3D12EncodeCopy as D3D12EncodeCopy,
};
unsafe extern "C" {
fn alpha2_native_gpu_d3d12_pool_create(device: *mut c_void) -> *mut c_void;
fn alpha2_native_gpu_d3d12_pool_destroy(pool: *mut c_void);
fn alpha2_native_gpu_d3d12_pool_trim(pool: *mut c_void) -> u64;
#[cfg(test)]
fn alpha2_native_gpu_d3d12_pool_counts(
pool: *mut c_void,
targets_made: *mut u32,
slots_made: *mut u32,
idle_targets: *mut u32,
idle_slots: *mut u32,
);
fn alpha2_native_gpu_d3d12_copy(
pool: *mut c_void,
queue: *mut c_void,
source: *mut c_void,
decode_fence: *mut c_void,
decode_value: u64,
output_ticket: *mut *mut c_void,
output: *mut D3D12CopyInfo,
status: *mut i32,
) -> i32;
fn alpha2_native_gpu_d3d12_copy_destroy(ticket: *mut c_void) -> i32;
fn alpha2_native_gpu_d3d12_set_wait_timeout(milliseconds: u32);
fn alpha2_native_gpu_d3d12_wait_timeout() -> u32;
fn alpha2_native_gpu_d3d12_encode_copier_create(
device: *mut c_void,
output: *mut *mut c_void,
status: *mut i32,
) -> i32;
fn alpha2_native_gpu_d3d12_encode_copier_destroy(copier: *mut c_void, status: *mut i32) -> i32;
fn alpha2_native_gpu_d3d12_encode_copier_slots() -> u32;
fn alpha2_native_gpu_d3d12_encode_copier_await_slot(
copier: *mut c_void,
slot: *mut u32,
status: *mut i32,
) -> i32;
fn alpha2_native_gpu_d3d12_encode_copier_submit(
copier: *mut c_void,
queue: *mut c_void,
copy: *const D3D12EncodeCopy,
signal_value: *mut u64,
status: *mut i32,
) -> i32;
fn alpha2_native_gpu_d3d12_encode_caps(
device: *mut c_void,
codec: u32,
caps: *mut D3D12EncodeCaps,
status: *mut i32,
) -> i32;
}
pub(crate) fn set_wait_timeout(timeout: std::time::Duration) {
let milliseconds = crate::win32_wait_milliseconds(timeout);
unsafe { alpha2_native_gpu_d3d12_set_wait_timeout(milliseconds) };
}
#[must_use]
pub fn gpu_wait_timeout() -> std::time::Duration {
std::time::Duration::from_millis(u64::from(unsafe { alpha2_native_gpu_d3d12_wait_timeout() }))
}
#[allow(clippy::too_many_arguments)]
unsafe fn copy(
pool: *mut c_void,
queue: *mut c_void,
source: *mut c_void,
decode_fence: *mut c_void,
decode_value: u64,
ticket: &mut *mut c_void,
output: &mut D3D12CopyInfo,
status: &mut i32,
) -> i32 {
#[cfg(test)]
if let Some(step) = inject::fails(status) {
return step;
}
unsafe {
alpha2_native_gpu_d3d12_copy(
pool,
queue,
source,
decode_fence,
decode_value,
ticket,
output,
status,
)
}
}
unsafe fn encode_copy_submit(
copier: *mut c_void,
queue: *mut c_void,
copy: &D3D12EncodeCopy,
signalled: &mut u64,
status: &mut i32,
) -> i32 {
#[cfg(test)]
if let Some(step) = inject::encode_copy_fails(status) {
return step;
}
unsafe { alpha2_native_gpu_d3d12_encode_copier_submit(copier, queue, copy, signalled, status) }
}
#[cfg(test)]
pub(crate) mod inject {
use std::cell::Cell;
thread_local! {
static NEXT_COPY_FAILS_AT: Cell<(i32, i32)> = const { Cell::new((0, 0)) };
static NEXT_ENCODE_COPY_FAILS_AT: Cell<(i32, i32)> = const { Cell::new((0, 0)) };
}
pub(crate) fn fail_next_encode_copy(step: i32) {
NEXT_ENCODE_COPY_FAILS_AT.with(|next| next.set((step, 0x887A_0005_u32.cast_signed())));
}
pub(super) fn encode_copy_fails(status: &mut i32) -> Option<i32> {
let (step, chosen) = NEXT_ENCODE_COPY_FAILS_AT.with(|next| next.replace((0, 0)));
(step != 0).then(|| {
*status = chosen;
step
})
}
pub(crate) fn fail_next_copy(step: i32) {
fail_next_copy_with(step, 0x887A_0005_u32.cast_signed());
}
pub(crate) fn fail_next_copy_with(step: i32, status: i32) {
NEXT_COPY_FAILS_AT.with(|next| next.set((step, status)));
}
pub(super) fn fails(status: &mut i32) -> Option<i32> {
let (step, chosen) = NEXT_COPY_FAILS_AT.with(|next| next.replace((0, 0)));
(step != 0).then(|| {
*status = chosen;
step
})
}
}
#[derive(Debug)]
pub struct SharedD3D12Error(String);
impl fmt::Display for SharedD3D12Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.0)
}
}
impl Error for SharedD3D12Error {}
struct CopyTicket {
raw: NonNull<c_void>,
source: ManuallyDrop<Arc<D3D12FrameLease>>,
}
unsafe impl Send for CopyTicket {}
unsafe impl Sync for CopyTicket {}
impl Drop for CopyTicket {
fn drop(&mut self) {
let released = unsafe { alpha2_native_gpu_d3d12_copy_destroy(self.raw.as_ptr()) } != 0;
let source = unsafe { ManuallyDrop::take(&mut self.source) };
if released {
drop(source);
} else {
std::mem::forget(source);
}
}
}
struct CopyPool(NonNull<c_void>);
unsafe impl Send for CopyPool {}
unsafe impl Sync for CopyPool {}
impl Drop for CopyPool {
fn drop(&mut self) {
unsafe { alpha2_native_gpu_d3d12_pool_destroy(self.0.as_ptr()) };
}
}
#[derive(Clone)]
pub struct SharedD3D12Context {
gpu: GpuContext,
ffmpeg_device: AVHWDeviceContext,
identity: NativeGpuIdentity,
copies: Arc<CopyPool>,
decode_planes: bool,
encode_support: Arc<[OnceLock<Result<D3D12EncodeExtent, String>>; 3]>,
}
impl fmt::Debug for SharedD3D12Context {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SharedD3D12Context")
.field("identity", &self.identity)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct ImportedD3D12Frame {
pub y: wgpu::TextureView,
pub uv: wgpu::TextureView,
pub layout: NativePlaneLayout,
pub width: u32,
pub height: u32,
pub lease: Arc<D3D12CopyLease>,
}
pub struct D3D12CopyLease {
_ticket: CopyTicket,
_texture: wgpu::Texture,
gpu: GpuContext,
fence: ID3D12Fence,
fence_value: u64,
staged: AtomicBool,
submitted: AtomicBool,
retained_bytes: u64,
}
impl fmt::Debug for D3D12CopyLease {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("D3D12CopyLease")
.field("staged", &self.staged.load(Ordering::Acquire))
.field("submitted", &self.submitted.load(Ordering::Acquire))
.field("retained_bytes", &self.retained_bytes)
.finish_non_exhaustive()
}
}
impl D3D12CopyLease {
#[must_use]
pub const fn retained_bytes(&self) -> u64 {
self.retained_bytes
}
pub fn prepare_submission(&self) {
if self.staged.swap(true, Ordering::AcqRel) {
return;
}
if let Some(queue) = unsafe { self.gpu.queue().as_hal::<wgpu_hal::api::Dx12>() } {
queue.add_wait_fence(self.fence.clone(), self.fence_value);
}
}
pub fn submitted(&self) {
self.submitted.store(true, Ordering::Release);
}
}
impl Drop for D3D12CopyLease {
fn drop(&mut self) {
if !self.staged.load(Ordering::Acquire) || self.submitted.load(Ordering::Acquire) {
return;
}
if let Some(queue) = unsafe { self.gpu.queue().as_hal::<wgpu_hal::api::Dx12>() } {
queue.remove_wait_fence(&self.fence);
}
}
}
impl SharedD3D12Context {
pub fn from_gpu(gpu: GpuContext) -> Result<Self, SharedD3D12Error> {
let decode_planes = gpu
.device()
.features()
.contains(wgpu::Features::TEXTURE_FORMAT_NV12);
let hal = unsafe { gpu.device().as_hal::<wgpu_hal::api::Dx12>() }
.ok_or_else(|| SharedD3D12Error("renderer is not using D3D12".into()))?;
let raw = hal.raw_device().as_raw();
let identity = NativeGpuIdentity {
physical_device: raw.addr() as u64,
device: raw.addr() as u64,
epoch: 1,
};
let ffmpeg_device = unsafe { create_d3d12_device_context(raw.addr() as u64) }
.map_err(|error| SharedD3D12Error(error.to_string()))?;
let copies = NonNull::new(unsafe { alpha2_native_gpu_d3d12_pool_create(raw) })
.map(CopyPool)
.ok_or_else(|| SharedD3D12Error("out of memory for the D3D12 copy pool".into()))?;
drop(hal);
Ok(Self {
gpu,
ffmpeg_device,
identity,
copies: Arc::new(copies),
decode_planes,
encode_support: Arc::new([OnceLock::new(), OnceLock::new(), OnceLock::new()]),
})
}
pub fn for_decode(gpu: GpuContext) -> Result<Self, SharedD3D12Error> {
let shared = Self::from_gpu(gpu)?;
if !shared.decode_planes {
return Err(SharedD3D12Error(
"D3D12 adapter does not expose NV12 plane views".into(),
));
}
Ok(shared)
}
#[must_use]
pub const fn decode_planes(&self) -> bool {
self.decode_planes
}
pub fn encode_support(&self, codec: D3D12EncodeCodec) -> Result<D3D12EncodeExtent, String> {
self.encode_support[codec as usize]
.get_or_init(|| query_encode_support(&self.gpu, codec))
.clone()
}
#[must_use]
pub fn adapter(&self) -> Option<D3D12AdapterIdentity> {
d3d12_adapter_identity(self.gpu.adapter())
}
#[must_use]
pub fn gpu(&self) -> GpuContext {
self.gpu.clone()
}
#[must_use]
pub fn ffmpeg_device(&self) -> AVHWDeviceContext {
self.ffmpeg_device.clone()
}
#[must_use]
pub fn supports_ten_bit(&self) -> bool {
self.gpu
.device()
.features()
.contains(wgpu::Features::TEXTURE_FORMAT_P010)
}
#[must_use]
pub const fn identity(&self) -> NativeGpuIdentity {
self.identity
}
#[must_use]
pub fn release_idle_copies(&self) -> u64 {
unsafe { alpha2_native_gpu_d3d12_pool_trim(self.copies.0.as_ptr()) }
}
#[allow(clippy::too_many_lines)]
pub fn copy_surface(
&self,
frame: Arc<D3D12FrameLease>,
) -> Result<ImportedD3D12Frame, BridgeError> {
if !self.decode_planes {
return Err(BridgeError::InvalidInput(
"D3D12 adapter does not expose NV12 plane views",
));
}
let source = frame.info()?;
if source.device != self.identity.device {
return Err(BridgeError::InvalidInput("D3D12 frame device mismatch"));
}
let layout = match (source.software_format, source.dxgi_format) {
(rsmpeg::ffi::AV_PIX_FMT_NV12, DXGI_FORMAT_NV12) => NativePlaneLayout::Nv12,
(rsmpeg::ffi::AV_PIX_FMT_P010LE, DXGI_FORMAT_P010) => NativePlaneLayout::P010,
_ => {
return Err(BridgeError::InvalidInput("D3D12 frame is not NV12 or P010"));
}
};
if layout == NativePlaneLayout::P010
&& !self
.gpu
.device()
.features()
.contains(wgpu::Features::TEXTURE_FORMAT_P010)
{
return Err(BridgeError::InvalidInput(
"D3D12 adapter does not expose P010 plane views",
));
}
let queue = unsafe { self.gpu.queue().as_hal::<wgpu_hal::api::Dx12>() }
.ok_or(BridgeError::InvalidInput("renderer queue is not D3D12"))?;
let mut ticket = std::ptr::null_mut();
let mut copied = D3D12CopyInfo::default();
let mut status = 0;
let source_resource = raw_pointer(source.resource, "invalid D3D12 resource pointer")?;
let source_fence = raw_pointer(source.fence, "invalid D3D12 fence pointer")?;
let step = unsafe {
copy(
self.copies.0.as_ptr(),
queue.as_raw().as_raw(),
source_resource,
source_fence,
source.fence_value,
&mut ticket,
&mut copied,
&mut status,
)
};
drop(queue);
if step != 0 {
if step == COPY_SUBMITTED_UNFENCED {
std::mem::forget(frame);
}
return Err(crate::status::helper_failure(
alpha2_ffmpeg_bridge::NativeGpuBackend::D3D12,
"surface copy",
step,
status,
));
}
let ticket = CopyTicket {
raw: NonNull::new(ticket)
.ok_or(BridgeError::InvalidInput("D3D12 copy returned no ticket"))?,
source: ManuallyDrop::new(frame),
};
let raw_resource = raw_pointer(copied.resource, "invalid copied resource pointer")?;
let raw_fence = raw_pointer(copied.fence, "invalid copy fence pointer")?;
let resource = unsafe { ID3D12Resource::from_raw_borrowed(&raw_resource) }
.ok_or(BridgeError::InvalidInput("D3D12 copy returned no resource"))?
.clone();
let fence = unsafe { ID3D12Fence::from_raw_borrowed(&raw_fence) }
.ok_or(BridgeError::InvalidInput("D3D12 copy returned no fence"))?
.clone();
let size = wgpu::Extent3d {
width: copied.width,
height: copied.height,
depth_or_array_layers: 1,
};
let (texture_format, luma_format, chroma_format) = match layout {
NativePlaneLayout::P010 => (
wgpu::TextureFormat::P010,
wgpu::TextureFormat::R16Unorm,
wgpu::TextureFormat::Rg16Unorm,
),
_ => (
wgpu::TextureFormat::NV12,
wgpu::TextureFormat::R8Unorm,
wgpu::TextureFormat::Rg8Unorm,
),
};
let hal_texture = unsafe {
wgpu_hal::dx12::Device::texture_from_raw(
resource,
texture_format,
wgpu::TextureDimension::D2,
size,
1,
1,
)
};
let descriptor = wgpu::TextureDescriptor {
label: Some("alpha2 D3D12VA decode copy"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: texture_format,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
};
let texture = unsafe {
self.gpu
.device()
.create_texture_from_hal::<wgpu_hal::api::Dx12>(
hal_texture,
&descriptor,
wgpu::TextureUses::RESOURCE,
)
};
let y = texture.create_view(&wgpu::TextureViewDescriptor {
format: Some(luma_format),
aspect: wgpu::TextureAspect::Plane0,
..Default::default()
});
let uv = texture.create_view(&wgpu::TextureViewDescriptor {
format: Some(chroma_format),
aspect: wgpu::TextureAspect::Plane1,
..Default::default()
});
let bytes = u64::from(copied.width)
.saturating_mul(u64::from(copied.height))
.saturating_mul(layout.bytes_per_pixel_numerator());
let lease = Arc::new(D3D12CopyLease {
_ticket: ticket,
_texture: texture,
gpu: self.gpu.clone(),
fence,
fence_value: copied.fence_value,
staged: AtomicBool::new(false),
submitted: AtomicBool::new(false),
retained_bytes: bytes,
});
Ok(ImportedD3D12Frame {
y,
uv,
layout,
width: copied.width,
height: copied.height,
lease,
})
}
}
fn helper_error(operation: &'static str, step: i32, status: i32) -> BridgeError {
crate::status::helper_failure(
alpha2_ffmpeg_bridge::NativeGpuBackend::D3D12,
operation,
step,
status,
)
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct D3D12AdapterIdentity {
pub luid: u64,
pub software: bool,
}
#[must_use]
pub fn d3d12_adapter_identity(adapter: &wgpu::Adapter) -> Option<D3D12AdapterIdentity> {
let hal = unsafe { adapter.as_hal::<wgpu_hal::api::Dx12>() }?;
let description = unsafe { hal.raw_adapter().GetDesc1() }.ok()?;
let luid = (u64::from(description.AdapterLuid.HighPart.cast_unsigned()) << 32)
| u64::from(description.AdapterLuid.LowPart);
let software = description.Flags
& windows::Win32::Graphics::Dxgi::DXGI_ADAPTER_FLAG_SOFTWARE
.0
.cast_unsigned()
!= 0;
Some(D3D12AdapterIdentity { luid, software })
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum D3D12EncodeCodec {
H264,
Hevc,
Av1,
}
impl D3D12EncodeCodec {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::H264 => "h264",
Self::Hevc => "hevc",
Self::Av1 => "av1",
}
}
#[must_use]
pub const fn profile(self) -> &'static str {
match self {
Self::H264 | Self::Hevc | Self::Av1 => "main",
}
}
const fn abi(self) -> u32 {
match self {
Self::H264 => crate::interop::ALPHA2_D3D12_ENCODE_H264_MAIN,
Self::Hevc => crate::interop::ALPHA2_D3D12_ENCODE_HEVC_MAIN,
Self::Av1 => crate::interop::ALPHA2_D3D12_ENCODE_AV1_MAIN,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct D3D12EncodeExtent {
pub codec: D3D12EncodeCodec,
pub profile: &'static str,
pub minimum: (u32, u32),
pub maximum: (u32, u32),
pub granularity: (u32, u32),
pub max_p_picture_l0_references: u32,
pub max_b_picture_l0_references: u32,
pub max_l1_references: u32,
pub rate_control_modes: Vec<&'static str>,
pub refused_rate_control_modes: Vec<(&'static str, i32)>,
}
impl D3D12EncodeExtent {
#[must_use]
pub const fn supports_b_pictures(&self) -> bool {
self.max_b_picture_l0_references > 0 || self.max_l1_references > 0
}
#[must_use]
pub const fn accepts(&self, width: u32, height: u32) -> bool {
let coded = self.coded(width, height);
coded.0 >= self.minimum.0
&& coded.1 >= self.minimum.1
&& coded.0 <= self.maximum.0
&& coded.1 <= self.maximum.1
}
#[must_use]
pub const fn coded(&self, width: u32, height: u32) -> (u32, u32) {
(
align_up(width, self.granularity.0),
align_up(height, self.granularity.1),
)
}
}
const fn align_up(value: u32, granularity: u32) -> u32 {
if granularity <= 1 {
return value;
}
value.div_ceil(granularity).saturating_mul(granularity)
}
const fn encode_check_name(check: u32) -> &'static str {
use crate::interop::{
ALPHA2_D3D12_ENCODE_CHECK_CODEC, ALPHA2_D3D12_ENCODE_CHECK_CONFIGURATION,
ALPHA2_D3D12_ENCODE_CHECK_INPUT_FORMAT, ALPHA2_D3D12_ENCODE_CHECK_PICTURE_CONTROL,
ALPHA2_D3D12_ENCODE_CHECK_PROFILE_LEVEL, ALPHA2_D3D12_ENCODE_CHECK_RATE_CONTROL,
ALPHA2_D3D12_ENCODE_CHECK_RESOLUTION, ALPHA2_D3D12_ENCODE_CHECK_SUPPORT,
ALPHA2_D3D12_ENCODE_CHECK_VIDEO_DEVICE,
};
match check {
ALPHA2_D3D12_ENCODE_CHECK_VIDEO_DEVICE => "ID3D12VideoDevice3",
ALPHA2_D3D12_ENCODE_CHECK_CODEC => "codec",
ALPHA2_D3D12_ENCODE_CHECK_PROFILE_LEVEL => "profile and level",
ALPHA2_D3D12_ENCODE_CHECK_INPUT_FORMAT => "NV12 input",
ALPHA2_D3D12_ENCODE_CHECK_RATE_CONTROL => "rate-control mode",
ALPHA2_D3D12_ENCODE_CHECK_RESOLUTION => "output resolution",
ALPHA2_D3D12_ENCODE_CHECK_PICTURE_CONTROL => "picture control",
ALPHA2_D3D12_ENCODE_CHECK_CONFIGURATION => "codec configuration",
ALPHA2_D3D12_ENCODE_CHECK_SUPPORT => "D3D12_FEATURE_VIDEO_ENCODER_SUPPORT",
_ => "an unknown",
}
}
const RATE_CONTROL_MODES: [(u32, &str); 4] = [
(crate::interop::ALPHA2_D3D12_RATE_CONTROL_CQP, "disabled"),
(crate::interop::ALPHA2_D3D12_RATE_CONTROL_CBR, "cbr"),
(crate::interop::ALPHA2_D3D12_RATE_CONTROL_VBR, "vbr"),
(crate::interop::ALPHA2_D3D12_RATE_CONTROL_QVBR, "qvbr"),
];
fn encode_status(status: i32) -> String {
if status == 0 {
"not supported".to_owned()
} else {
format!("HRESULT {status:#010X}")
}
}
fn encode_refusal(adapter: &str, codec: D3D12EncodeCodec, caps: &D3D12EncodeCaps) -> String {
let (name, profile) = (codec.name(), codec.profile());
let check = caps.refused_check;
let status = caps.refused_status;
if check == crate::interop::ALPHA2_D3D12_ENCODE_CHECK_VIDEO_DEVICE {
return format!(
"{adapter}: ID3D12VideoDevice3 unavailable ({})",
encode_status(status)
);
}
if check == crate::interop::ALPHA2_D3D12_ENCODE_CHECK_CODEC && status == 0 {
return format!("{adapter} reports no D3D12 video encoder for {name}");
}
let detail = if check == crate::interop::ALPHA2_D3D12_ENCODE_CHECK_SUPPORT {
let modes: Vec<String> = RATE_CONTROL_MODES
.iter()
.zip(caps.rate_control_status)
.filter(|((bit, _), _)| caps.refused_rate_control_modes & bit != 0)
.map(|(&(_, mode), status)| format!("{mode}: {}", encode_status(status)))
.collect();
let flags = caps.validation_flags;
let flags = if flags == 0 {
String::new()
} else {
format!("; validation flags {flags:#X}")
};
format!("{}{flags}", modes.join(", "))
} else {
encode_status(status)
};
format!(
"{adapter}: D3D12 Video Encode refuses {name} {profile} at the {} check ({detail})",
encode_check_name(check)
)
}
fn query_encode_support(
gpu: &GpuContext,
codec: D3D12EncodeCodec,
) -> Result<D3D12EncodeExtent, String> {
let adapter = gpu.capabilities().adapter_name.clone();
let hal = unsafe { gpu.device().as_hal::<wgpu_hal::api::Dx12>() }
.ok_or_else(|| format!("{adapter}: the renderer is not using D3D12"))?;
let mut caps = D3D12EncodeCaps::default();
let mut status = 0;
let step = unsafe {
alpha2_native_gpu_d3d12_encode_caps(
hal.raw_device().as_raw(),
codec.abi(),
&raw mut caps,
&raw mut status,
)
};
drop(hal);
if step != 0 {
return Err(format!(
"{adapter}: {}",
helper_error("encode capability query", step, status)
));
}
if caps.supported == 0 {
return Err(encode_refusal(&adapter, codec, &caps));
}
let modes = RATE_CONTROL_MODES;
Ok(D3D12EncodeExtent {
codec,
profile: codec.profile(),
minimum: (caps.min_width, caps.min_height),
maximum: (caps.max_width, caps.max_height),
granularity: (caps.width_multiple.max(1), caps.height_multiple.max(1)),
max_p_picture_l0_references: caps.max_p_l0_references,
max_b_picture_l0_references: caps.max_b_l0_references,
max_l1_references: caps.max_l1_references,
rate_control_modes: modes
.iter()
.filter(|(bit, _)| caps.rate_control_modes & bit != 0)
.map(|&(_, name)| name)
.collect(),
refused_rate_control_modes: modes
.iter()
.zip(caps.rate_control_status)
.filter(|((bit, _), _)| caps.refused_rate_control_modes & bit != 0)
.map(|(&(_, name), status)| (name, status))
.collect(),
})
}
pub struct D3D12EncodeCopier {
copier: *mut c_void,
retained: Option<Box<RetainedEncodeWork>>,
close_failed: bool,
}
struct RetainedEncodeWork {
copies: Vec<Option<EncodeCopyInputs>>,
context: SharedD3D12Context,
#[cfg(test)]
lifetime_probe: Option<Arc<()>>,
}
struct EncodeCopyInputs {
_luma: wgpu::Texture,
_chroma: wgpu::Texture,
_frame: rsmpeg::avutil::AVFrame,
}
unsafe impl Send for D3D12EncodeCopier {}
impl fmt::Debug for D3D12EncodeCopier {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("D3D12EncodeCopier")
.finish_non_exhaustive()
}
}
impl Drop for D3D12EncodeCopier {
fn drop(&mut self) {
if !self.copier.is_null() && !self.close_failed {
let _ = self.try_close();
}
if !self.copier.is_null() {
std::mem::forget(self.retained.take());
}
}
}
impl D3D12EncodeCopier {
pub fn new(context: &SharedD3D12Context) -> Result<Self, BridgeError> {
let gpu = context.gpu();
let hal = unsafe { gpu.device().as_hal::<wgpu_hal::api::Dx12>() }
.ok_or(BridgeError::InvalidInput("renderer is not D3D12"))?;
let mut copier = std::ptr::null_mut();
let mut status = 0;
let step = unsafe {
alpha2_native_gpu_d3d12_encode_copier_create(
hal.raw_device().as_raw(),
&raw mut copier,
&raw mut status,
)
};
drop(hal);
if step != 0 {
return Err(helper_error("encode copier creation", step, status));
}
Ok(Self {
copier,
retained: Some(Box::new(RetainedEncodeWork {
copies: (0..Self::slots()).map(|_| None).collect(),
context: context.clone(),
#[cfg(test)]
lifetime_probe: None,
})),
close_failed: false,
})
}
#[must_use]
pub fn slots() -> usize {
unsafe { alpha2_native_gpu_d3d12_encode_copier_slots() as usize }
}
pub fn await_slot(&mut self) -> Result<usize, BridgeError> {
let (mut slot, mut status) = (0_u32, 0);
let step = unsafe {
alpha2_native_gpu_d3d12_encode_copier_await_slot(
self.copier,
&raw mut slot,
&raw mut status,
)
};
if step != 0 {
return Err(helper_error("encode copy slot", step, status));
}
if let Some(retained) = self.retained.as_mut() {
retained.copies[slot as usize] = None;
}
Ok(slot as usize)
}
pub fn submit(
&mut self,
frame: &mut alpha2_ffmpeg_bridge::D3D12EncodeFrameAccess<'_>,
luma: &wgpu::Texture,
chroma: &wgpu::Texture,
size: (u32, u32),
) -> Result<(), BridgeError> {
let slot = self.await_slot()?;
let target = *frame.info();
let retained = self
.retained
.as_mut()
.ok_or(BridgeError::InvalidInput("invalid encode copier slot"))?;
if target.device != retained.context.identity.device {
return Err(BridgeError::InvalidInput("D3D12 frame device mismatch"));
}
let inputs = EncodeCopyInputs {
_luma: luma.clone(),
_chroma: chroma.clone(),
_frame: frame.retain_frame()?,
};
let luma_resource = unsafe { luma.as_hal::<wgpu_hal::api::Dx12>() }
.map(|texture| unsafe { texture.raw_resource() }.as_raw().addr() as u64)
.ok_or(BridgeError::InvalidInput("luma plane is not D3D12"))?;
let chroma_resource = unsafe { chroma.as_hal::<wgpu_hal::api::Dx12>() }
.map(|texture| unsafe { texture.raw_resource() }.as_raw().addr() as u64)
.ok_or(BridgeError::InvalidInput("chroma plane is not D3D12"))?;
let copy = D3D12EncodeCopy {
luma: luma_resource,
chroma: chroma_resource,
destination: target.resource,
frame_fence: target.fence,
frame_value: target.fence_value,
width: size.0,
height: size.1,
};
let gpu = retained.context.gpu();
let queue = unsafe { gpu.queue().as_hal::<wgpu_hal::api::Dx12>() }
.ok_or(BridgeError::InvalidInput("renderer queue is not D3D12"))?;
let (mut signalled, mut status) = (0_u64, 0);
let step = unsafe {
encode_copy_submit(
self.copier,
queue.as_raw().as_raw(),
©,
&mut signalled,
&mut status,
)
};
drop(queue);
if step != 0 {
if step == COPY_SUBMITTED_UNFENCED {
std::mem::forget(inputs);
}
return Err(helper_error("encode copy", step, status));
}
retained.copies[slot] = Some(inputs);
frame.commit(signalled)
}
pub fn finish(mut self) -> Result<(), BridgeError> {
self.try_close()
}
fn try_close(&mut self) -> Result<(), BridgeError> {
let mut status = 0;
let step =
unsafe { alpha2_native_gpu_d3d12_encode_copier_destroy(self.copier, &raw mut status) };
if step != 0 {
self.close_failed = true;
return Err(helper_error("encode copier teardown", step, status));
}
self.copier = std::ptr::null_mut();
Ok(())
}
}
#[cfg(test)]
impl SharedD3D12Context {
fn copy_pool_counts(&self) -> [u32; 4] {
let mut counts = [0; 4];
let [made, slots, idle, idle_slots] = &mut counts;
unsafe {
alpha2_native_gpu_d3d12_pool_counts(
self.copies.0.as_ptr(),
made,
slots,
idle,
idle_slots,
);
}
counts
}
}
fn raw_pointer(value: u64, message: &'static str) -> Result<*mut c_void, BridgeError> {
Ok(usize::try_from(value).map_err(|_| BridgeError::InvalidInput(message))? as *mut c_void)
}
#[cfg(test)]
#[path = "d3d12_tests.rs"]
mod tests;