1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
//! Defines a wrapper around
//! [`NVENCSTATUS`](crate::sys::nvEncodeAPI::NVENCSTATUS) to provide ergonomic
//! error handling.

use std::{error::Error, ffi::CStr, fmt};

use super::{api::ENCODE_API, encoder::Encoder};
use crate::sys::nvEncodeAPI::NVENCSTATUS;

/// Wrapper enum around [`NVENCSTATUS`].
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum ErrorKind {
    /// No encode capable devices were detected.
    NoEncodeDevice = 1,
    /// The device passed by the client is not supported.
    UnsupportedDevice = 2,
    /// The encoder device supplied by the client is not valid.
    InvalidEncoderDevice = 3,
    /// The device passed to the API call is invalid.
    InvalidDevice = 4,
    /// The device passed to the API call is no longer available
    /// and needs to be reinitialized. The clients need to destroy the
    /// current encoder session by freeing the allocated input output
    /// buffers and destroying the device and create a new encoding session.
    DeviceNotExist = 5,
    /// One or more of the pointers passed to the API call is invalid.
    InvalidPtr = 6,
    /// The completion event passed in the [`EncodeAPI.encode_picture`]
    /// call is invalid.
    InvalidEvent = 7,
    /// One or more of the parameter passed to the API call is invalid.
    InvalidParam = 8,
    /// An API call was made in wrong sequence or order.
    InvalidCall = 9,
    /// the API call failed because it was unable to allocate enough memory
    /// to perform the requested operation.
    OutOfMemory = 10,
    /// The encoder has not been initialized with
    /// [`EncodeAPI.initialize_encoder`] or that initialization has failed.
    /// The client cannot allocate input or output buffers or do any encoding
    /// related operation before successfully initializing the encoder.
    EncoderNotInitialized = 11,
    /// An unsupported parameter was passed by the client.
    UnsupportedParam = 12,
    /// The [`EncodeAPI.lock_bitstream`] failed to lock the output
    /// buffer. This happens when the client makes a non-blocking lock call
    /// to access the output bitstream by passing the `doNotWait` flag.
    /// This is not a fatal error and client should retry the same operation
    /// after few milliseconds.
    LockBusy = 13,
    /// The size of the user buffer passed by the client is insufficient for
    /// the requested operation.
    NotEnoughBuffer = 14,
    /// An invalid struct version was used by the client.
    InvalidVersion = 15,
    /// [`EncodeAPI.map_input_resource`] failed to map the client provided
    /// input resource.
    MapFailed = 16,
    /// The encode driver requires more input buffers to produce an output
    /// bitstream. If this error is returned from [`EncodeAPI.encode_picture`],
    /// this is not a fatal error. If the client is encoding with B frames
    /// then, [`EncodeAPI.encode_picture`] might be buffering the input
    /// frame for re-ordering.
    ///
    /// A client operating in synchronous mode cannot call
    /// [`EncodeAPI.lock_bitstream`] on the output bitstream buffer if
    /// [`EncodeAPI.encode_picture`] returned this variant. The client must
    /// continue providing input frames until encode driver returns
    /// successfully. After a success the client
    /// can call [`EncodeAPI.lock_bitstream`] on the output buffers in the
    /// same order in which it has called [`EncodeAPI.encode_picture`].
    NeedMoreInput = 17,
    /// The hardware encoder is busy encoding and is unable to encode
    /// the input. The client should call [EncodeAPI.encode_picture] again after
    /// few milliseconds.
    EncoderBusy = 18,
    /// The completion event passed in [`EncodeAPI.encode_picture`]
    /// has not been registered with encoder driver using
    /// [`EncodeAPI.register_async_event`].
    EventNotRegistered = 19,
    /// An unknown internal error has occurred.
    Generic = 20,
    /// The client is attempting to use a feature
    /// that is not available for the license type for the current system.
    IncompatibleClientKey = 21,
    /// the client is attempting to use a feature
    /// that is not implemented for the current version.
    Unimplemented = 22,
    /// [`EncodeAPI.register_resource`] failed to register the resource.
    ResourceRegisterFailed = 23,
    /// The client is attempting to unregister a resource
    /// that has not been successfully registered.
    ResourceNotRegistered = 24,
    /// The client is attempting to unmap a resource
    /// that has not been successfully mapped.
    ResourceNotMapped = 25,
    /// The encode driver requires more output buffers to write an
    /// output bitstream. If this error is returned from
    /// [`EncodeAPI.restore_encoder_state`], this is not a fatal error. If the
    /// client is encoding with B frames then,
    /// [`EncodeAPI.restore_encoder_state`] API might be requiring the extra
    /// output buffer for accommodating overlay frame output in a separate
    /// buffer, for AV1 codec. In this case, the client must call
    /// [`EncodeAPI.restore_encoder_state`] API again with
    /// an output bitstream as input along with the parameters in the previous
    /// call. When operating in asynchronous mode of encoding, client must
    /// also specify the completion event.
    NeedMoreOutput = 26,
}

/// Wrapper struct around [`NVENCSTATUS`].
///
/// This struct also contains a string with additional info
/// when it is relevant and available.
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct EncodeError {
    kind: ErrorKind,
    string: Option<String>,
}

impl EncodeError {
    /// Getter for the error kind.
    #[must_use]
    pub fn kind(&self) -> ErrorKind {
        self.kind
    }

    /// Getter for the error string.
    #[must_use]
    pub fn string(&self) -> Option<&str> {
        self.string.as_deref()
    }
}

impl fmt::Display for EncodeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.string {
            Some(s) => write!(f, "{:?}: {s}", self.kind),
            None => write!(f, "{:?}", self.kind),
        }
    }
}

impl Error for EncodeError {}

impl From<NVENCSTATUS> for ErrorKind {
    fn from(status: NVENCSTATUS) -> Self {
        match status {
            NVENCSTATUS::NV_ENC_SUCCESS => {
                unreachable!("Success should not be converted to an error.")
            }
            NVENCSTATUS::NV_ENC_ERR_NO_ENCODE_DEVICE => Self::NoEncodeDevice,
            NVENCSTATUS::NV_ENC_ERR_UNSUPPORTED_DEVICE => Self::UnsupportedDevice,
            NVENCSTATUS::NV_ENC_ERR_INVALID_ENCODERDEVICE => Self::InvalidEncoderDevice,
            NVENCSTATUS::NV_ENC_ERR_INVALID_DEVICE => Self::InvalidDevice,
            NVENCSTATUS::NV_ENC_ERR_DEVICE_NOT_EXIST => Self::DeviceNotExist,
            NVENCSTATUS::NV_ENC_ERR_INVALID_PTR => Self::InvalidPtr,
            NVENCSTATUS::NV_ENC_ERR_INVALID_EVENT => Self::InvalidEvent,
            NVENCSTATUS::NV_ENC_ERR_INVALID_PARAM => Self::InvalidParam,
            NVENCSTATUS::NV_ENC_ERR_INVALID_CALL => Self::InvalidCall,
            NVENCSTATUS::NV_ENC_ERR_OUT_OF_MEMORY => Self::OutOfMemory,
            NVENCSTATUS::NV_ENC_ERR_ENCODER_NOT_INITIALIZED => Self::EncoderNotInitialized,
            NVENCSTATUS::NV_ENC_ERR_UNSUPPORTED_PARAM => Self::UnsupportedParam,
            NVENCSTATUS::NV_ENC_ERR_LOCK_BUSY => Self::LockBusy,
            NVENCSTATUS::NV_ENC_ERR_NOT_ENOUGH_BUFFER => Self::NotEnoughBuffer,
            NVENCSTATUS::NV_ENC_ERR_INVALID_VERSION => Self::InvalidVersion,
            NVENCSTATUS::NV_ENC_ERR_MAP_FAILED => Self::MapFailed,
            NVENCSTATUS::NV_ENC_ERR_NEED_MORE_INPUT => Self::NeedMoreInput,
            NVENCSTATUS::NV_ENC_ERR_ENCODER_BUSY => Self::EncoderBusy,
            NVENCSTATUS::NV_ENC_ERR_EVENT_NOT_REGISTERD => Self::EventNotRegistered,
            NVENCSTATUS::NV_ENC_ERR_GENERIC => Self::Generic,
            NVENCSTATUS::NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY => Self::IncompatibleClientKey,
            NVENCSTATUS::NV_ENC_ERR_UNIMPLEMENTED => Self::Unimplemented,
            NVENCSTATUS::NV_ENC_ERR_RESOURCE_REGISTER_FAILED => Self::ResourceRegisterFailed,
            NVENCSTATUS::NV_ENC_ERR_RESOURCE_NOT_REGISTERED => Self::ResourceNotRegistered,
            NVENCSTATUS::NV_ENC_ERR_RESOURCE_NOT_MAPPED => Self::ResourceNotMapped,
            NVENCSTATUS::NV_ENC_ERR_NEED_MORE_OUTPUT => Self::NeedMoreOutput,
        }
    }
}

impl NVENCSTATUS {
    /// Convert an [`NVENCSTATUS`] to a [`Result`].
    ///
    /// [`NVENCSTATUS::NV_ENC_SUCCESS`] is converted to `Ok(())`,
    /// and all other variants are mapped to the corresponding variant
    /// in [`ErrorKind`]. The error type is [`EncodeError`] which has
    /// a kind and an optional `String` which might contain additional
    /// information about the error.
    ///
    /// # Errors
    ///
    /// Returns an error whenever the status is not
    /// [`NVENCSTATUS::NV_ENC_SUCCESS`].
    ///
    /// # Examples
    ///
    /// ```
    /// # use cudarc::driver::CudaDevice;
    /// # use nvidia_video_codec_sdk::{sys::nvEncodeAPI::GUID, EncodeError, Encoder, ErrorKind};
    /// # let cuda_device = CudaDevice::new(0).unwrap();
    /// let encoder = Encoder::initialize_with_cuda(cuda_device).unwrap();
    /// // Cause an error by passing in an invalid GUID.
    /// // `Encoder::get_supported_input_formats()` uses `.result()` internally
    /// let error = encoder
    ///     .get_supported_input_formats(GUID::default())
    ///     .unwrap_err();
    /// // Get the kind.
    /// assert_eq!(error.kind(), ErrorKind::InvalidParam);
    /// // Get the error message.
    /// // Unfortunately, it's not always helpful.
    /// assert_eq!(error.string(), Some("EncodeAPI Internal Error."));
    /// ```
    pub fn result(self, encoder: &Encoder) -> Result<(), EncodeError> {
        self.result_without_string().map_err(|mut err| {
            err.string = match err.kind {
                // Avoid getting the string if it is not needed.
                ErrorKind::LockBusy
                | ErrorKind::EncoderBusy
                | ErrorKind::NeedMoreInput
                | ErrorKind::OutOfMemory => None,
                // Otherwise allocate an owned `String` with the error.
                _ => Some(
                    unsafe { CStr::from_ptr((ENCODE_API.get_last_error_string)(encoder.ptr)) }
                        .to_string_lossy()
                        .to_string(),
                ),
            }
            .and_then(|s| if s.is_empty() { None } else { Some(s) });
            err
        })
    }

    /// Convert an [`NVENCSTATUS`] to a [`Result`] without
    /// using an [`Encoder`].
    ///
    /// This function is the same as [`NVENCSTATUS::result`] except
    /// it does not get the error string because it does not have access
    /// to an [`Encoder`]. This is only useful if you do not have an [`Encoder`]
    /// yet, for example when initializing the API.
    ///
    /// You should always prefer to use [`NVENCSTATUS::result`] when possible.
    ///
    /// # Errors
    ///
    /// Returns an error whenever the status is not
    /// [`NVENCSTATUS::NV_ENC_SUCCESS`].
    pub fn result_without_string(self) -> Result<(), EncodeError> {
        match self {
            Self::NV_ENC_SUCCESS => Ok(()),
            err => Err(EncodeError {
                kind: err.into(),
                string: None,
            }),
        }
    }
}