use alpha2_ffmpeg_bridge::{BridgeError, GpuFault, NativeGpuBackend, NativeGpuFailure};
use crate::interop::{
ALPHA2_GPU_STEP_ALLOCATE_COMMAND_BUFFERS, ALPHA2_GPU_STEP_ARGUMENTS,
ALPHA2_GPU_STEP_BEGIN_COMMAND_BUFFER, ALPHA2_GPU_STEP_CLOSE_COMMAND_LIST,
ALPHA2_GPU_STEP_CREATE_COMMAND_ALLOCATOR, ALPHA2_GPU_STEP_CREATE_COMMAND_LIST,
ALPHA2_GPU_STEP_CREATE_COMMAND_POOL, ALPHA2_GPU_STEP_CREATE_FENCE,
ALPHA2_GPU_STEP_CREATE_IMAGE_VIEW, ALPHA2_GPU_STEP_CREATE_RESOURCE,
ALPHA2_GPU_STEP_CREATE_SEMAPHORE, ALPHA2_GPU_STEP_CREATE_TEXTURE_FROM_IMAGE,
ALPHA2_GPU_STEP_END_COMMAND_BUFFER, ALPHA2_GPU_STEP_FENCE_STATUS,
ALPHA2_GPU_STEP_HOST_ALLOCATION, ALPHA2_GPU_STEP_LOADER, ALPHA2_GPU_STEP_NO_OUTPUT,
ALPHA2_GPU_STEP_QUEUE, ALPHA2_GPU_STEP_QUEUE_SIGNAL, ALPHA2_GPU_STEP_QUEUE_SUBMIT,
ALPHA2_GPU_STEP_QUEUE_WAIT, ALPHA2_GPU_STEP_RESET_COMMAND_BUFFER,
ALPHA2_GPU_STEP_RESET_COMMAND_POOL, ALPHA2_GPU_STEP_RESET_FENCE, ALPHA2_GPU_STEP_UNSUPPORTED,
ALPHA2_GPU_STEP_WAIT_FENCE, ALPHA2_GPU_STEP_WAIT_SEMAPHORE,
};
const fn step_name(step: i32) -> &'static str {
match step {
ALPHA2_GPU_STEP_ARGUMENTS => "argument check",
ALPHA2_GPU_STEP_UNSUPPORTED => "frame check",
ALPHA2_GPU_STEP_HOST_ALLOCATION => "host allocation",
ALPHA2_GPU_STEP_LOADER => "command loading",
ALPHA2_GPU_STEP_QUEUE => "queue lookup",
ALPHA2_GPU_STEP_NO_OUTPUT => "result check",
ALPHA2_GPU_STEP_CREATE_COMMAND_POOL => "command pool creation",
ALPHA2_GPU_STEP_ALLOCATE_COMMAND_BUFFERS => "command buffer allocation",
ALPHA2_GPU_STEP_CREATE_FENCE => "fence creation",
ALPHA2_GPU_STEP_CREATE_SEMAPHORE => "semaphore creation",
ALPHA2_GPU_STEP_CREATE_IMAGE_VIEW => "image view creation",
ALPHA2_GPU_STEP_BEGIN_COMMAND_BUFFER => "command buffer begin",
ALPHA2_GPU_STEP_END_COMMAND_BUFFER => "command buffer end",
ALPHA2_GPU_STEP_QUEUE_SUBMIT => "queue submit",
ALPHA2_GPU_STEP_WAIT_FENCE => "fence wait",
ALPHA2_GPU_STEP_WAIT_SEMAPHORE => "semaphore wait",
ALPHA2_GPU_STEP_RESET_FENCE => "fence reset",
ALPHA2_GPU_STEP_RESET_COMMAND_BUFFER => "command buffer reset",
ALPHA2_GPU_STEP_RESET_COMMAND_POOL => "command pool reset",
ALPHA2_GPU_STEP_FENCE_STATUS => "fence status",
ALPHA2_GPU_STEP_CREATE_RESOURCE => "resource creation",
ALPHA2_GPU_STEP_CREATE_COMMAND_ALLOCATOR => "command allocator creation",
ALPHA2_GPU_STEP_CREATE_COMMAND_LIST => "command list creation",
ALPHA2_GPU_STEP_CLOSE_COMMAND_LIST => "command list close",
ALPHA2_GPU_STEP_QUEUE_WAIT => "queue wait",
ALPHA2_GPU_STEP_QUEUE_SIGNAL => "queue signal",
ALPHA2_GPU_STEP_CREATE_TEXTURE_FROM_IMAGE => "texture creation",
_ => "an unknown step",
}
}
const fn vulkan_fault(status: i32) -> Option<GpuFault> {
match status {
2 => Some(GpuFault::Timeout),
-1 => Some(GpuFault::HostMemory),
-2 => Some(GpuFault::DeviceMemory),
-4 => Some(GpuFault::DeviceLost),
_ => None,
}
}
#[allow(clippy::cast_possible_wrap)] const fn d3d12_fault(status: i32) -> Option<GpuFault> {
const E_OUTOFMEMORY: i32 = 0x8007_000E_u32 as i32;
const DXGI_ERROR_DEVICE_REMOVED: i32 = 0x887A_0005_u32 as i32;
const DXGI_ERROR_DEVICE_HUNG: i32 = 0x887A_0006_u32 as i32;
const DXGI_ERROR_DEVICE_RESET: i32 = 0x887A_0007_u32 as i32;
const DXGI_ERROR_DRIVER_INTERNAL_ERROR: i32 = 0x887A_0020_u32 as i32;
const WAIT_TIMEOUT: i32 = 0x102;
match status {
WAIT_TIMEOUT => Some(GpuFault::Timeout),
E_OUTOFMEMORY => Some(GpuFault::DeviceMemory),
DXGI_ERROR_DEVICE_REMOVED
| DXGI_ERROR_DEVICE_HUNG
| DXGI_ERROR_DEVICE_RESET
| DXGI_ERROR_DRIVER_INTERNAL_ERROR => Some(GpuFault::DeviceLost),
_ => None,
}
}
const fn metal_fault(status: i32) -> Option<GpuFault> {
match status {
-6662 => Some(GpuFault::DeviceMemory),
_ => None,
}
}
pub(crate) fn helper_failure(
backend: NativeGpuBackend,
operation: &'static str,
step: i32,
status: i32,
) -> BridgeError {
match step {
ALPHA2_GPU_STEP_ARGUMENTS => {
return BridgeError::InvalidInput("native GPU helper arguments do not belong together");
}
ALPHA2_GPU_STEP_UNSUPPORTED => {
return BridgeError::InvalidInput("frame is not one the native GPU helper handles");
}
_ => {}
}
let fault = if step == ALPHA2_GPU_STEP_HOST_ALLOCATION {
Some(GpuFault::HostMemory)
} else {
match backend {
NativeGpuBackend::Vulkan => vulkan_fault(status),
NativeGpuBackend::D3D12 => d3d12_fault(status),
NativeGpuBackend::Metal => metal_fault(status),
}
};
BridgeError::NativeGpu(NativeGpuFailure {
backend,
operation,
step: step_name(step),
status,
fault,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn fault_of(error: &BridgeError) -> Option<GpuFault> {
error.gpu_fault()
}
#[test]
fn a_helper_status_names_its_backend_step_and_fault() {
let vulkan =
|step, status| helper_failure(NativeGpuBackend::Vulkan, "encode copy", step, status);
let cases = [
(
vulkan(ALPHA2_GPU_STEP_QUEUE_SUBMIT, -4),
Some(GpuFault::DeviceLost),
),
(
vulkan(ALPHA2_GPU_STEP_QUEUE_SUBMIT, -2),
Some(GpuFault::DeviceMemory),
),
(
vulkan(ALPHA2_GPU_STEP_CREATE_FENCE, -1),
Some(GpuFault::HostMemory),
),
(
vulkan(ALPHA2_GPU_STEP_WAIT_FENCE, 2),
Some(GpuFault::Timeout),
),
(
vulkan(ALPHA2_GPU_STEP_HOST_ALLOCATION, 0),
Some(GpuFault::HostMemory),
),
(vulkan(ALPHA2_GPU_STEP_CREATE_COMMAND_POOL, -3), None),
(vulkan(ALPHA2_GPU_STEP_LOADER, 0), None),
];
for (error, fault) in &cases {
assert_eq!(fault_of(error), *fault, "{error}");
let message = error.to_string();
assert!(!message.contains("FFmpeg"), "{message}");
assert!(message.starts_with("Vulkan encode copy"), "{message}");
}
assert_eq!(
cases[0].0.to_string(),
"Vulkan encode copy failed at queue submit (VkResult -4)"
);
assert!(!cases[1].0.to_string().contains("memory"));
assert!(
cases[3]
.0
.to_string()
.contains("GPU work did not complete within the wait timeout")
);
let d3d12 = |status: u32| {
helper_failure(
NativeGpuBackend::D3D12,
"surface copy",
ALPHA2_GPU_STEP_CREATE_RESOURCE,
status.cast_signed(),
)
};
let removed = d3d12(0x887A_0005);
assert_eq!(fault_of(&removed), Some(GpuFault::DeviceLost));
assert_eq!(
removed.to_string(),
"D3D12 surface copy failed at resource creation (HRESULT 0x887A0005)"
);
for hung in [0x887A_0006, 0x887A_0007, 0x887A_0020] {
assert_eq!(fault_of(&d3d12(hung)), Some(GpuFault::DeviceLost));
}
assert_eq!(fault_of(&d3d12(0x8007_000E)), Some(GpuFault::DeviceMemory));
assert_eq!(fault_of(&d3d12(0x8007_0057)), None);
let timed_out = helper_failure(
NativeGpuBackend::D3D12,
"encode copy slot",
ALPHA2_GPU_STEP_WAIT_FENCE,
0x102,
);
assert_eq!(fault_of(&timed_out), Some(GpuFault::Timeout));
let metal = |status| {
helper_failure(
NativeGpuBackend::Metal,
"texture import",
ALPHA2_GPU_STEP_CREATE_TEXTURE_FROM_IMAGE,
status,
)
};
assert_eq!(fault_of(&metal(-6662)), Some(GpuFault::DeviceMemory));
assert_eq!(fault_of(&metal(-6683)), None);
assert_eq!(
metal(-6683).to_string(),
"Metal texture import failed at texture creation (CVReturn -6683)"
);
assert!(matches!(
vulkan(ALPHA2_GPU_STEP_ARGUMENTS, 0),
BridgeError::InvalidInput(_)
));
assert!(matches!(
vulkan(ALPHA2_GPU_STEP_UNSUPPORTED, 0),
BridgeError::InvalidInput(_)
));
}
#[test]
fn an_ffmpeg_code_reads_as_ffmpeg_words_for_it() {
let message = BridgeError::FfmpegCode(-28).to_string();
assert!(message.starts_with("AVERROR(-28)"), "{message}");
assert!(!message.contains("FFmpeg error code"), "{message}");
}
}