#![forbid(unsafe_code)]
use crate::include::common::bitdepth::{BitDepth8, BitDepth16};
use crate::include::dav1d::data::Rav1dData;
use crate::include::dav1d::dav1d::{Rav1dDecodeFrameType, Rav1dInloopFilterType, Rav1dSettings};
use crate::include::dav1d::headers::{
Rav1dColorPrimaries, Rav1dMatrixCoefficients, Rav1dPixelLayout, Rav1dTransferCharacteristics,
};
use crate::include::dav1d::picture::{Rav1dPicture, Rav1dPictureDataComponentInner};
use crate::src::c_arc::CArc;
use crate::src::disjoint_mut::DisjointImmutGuard;
use crate::src::error::Rav1dError;
use crate::src::internal::Rav1dContext;
use std::ops::Deref;
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
InvalidSettings(&'static str),
InitFailed,
OutOfMemory,
InvalidData,
NeedMoreData,
Other(String),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidSettings(msg) => write!(f, "invalid settings: {}", msg),
Self::InitFailed => write!(f, "decoder initialization failed"),
Self::OutOfMemory => write!(f, "out of memory"),
Self::InvalidData => write!(f, "invalid data"),
Self::NeedMoreData => write!(f, "need more data"),
Self::Other(msg) => write!(f, "decode error: {}", msg),
}
}
}
impl std::error::Error for Error {}
impl From<Rav1dError> for Error {
fn from(err: Rav1dError) -> Self {
match err {
Rav1dError::EAGAIN => Self::NeedMoreData,
Rav1dError::ENOMEM => Self::OutOfMemory,
Rav1dError::EINVAL => Self::InvalidData,
Rav1dError::EGeneric => Self::Other("generic error".to_string()),
_ => Self::Other(format!("{:?}", err)),
}
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct Settings {
pub threads: u32,
pub apply_grain: bool,
pub frame_size_limit: u32,
pub all_layers: bool,
pub operating_point: u8,
pub output_invisible_frames: bool,
pub inloop_filters: InloopFilters,
pub decode_frame_type: DecodeFrameType,
pub max_frame_delay: u32,
pub strict_std_compliance: bool,
pub cpu_level: CpuLevel,
}
impl Default for Settings {
fn default() -> Self {
Self {
threads: 1,
apply_grain: true,
frame_size_limit: 8192 * 4320, all_layers: true,
operating_point: 0,
max_frame_delay: 0,
output_invisible_frames: false,
inloop_filters: InloopFilters::all(),
decode_frame_type: DecodeFrameType::All,
strict_std_compliance: false,
cpu_level: CpuLevel::Native,
}
}
}
impl From<Settings> for Rav1dSettings {
fn from(settings: Settings) -> Self {
Self {
n_threads: settings.threads as i32,
max_frame_delay: settings.max_frame_delay as i32,
apply_grain: settings.apply_grain,
operating_point: settings.operating_point,
all_layers: settings.all_layers,
frame_size_limit: settings.frame_size_limit,
allocator: Default::default(),
logger: None,
strict_std_compliance: settings.strict_std_compliance,
output_invisible_frames: settings.output_invisible_frames,
inloop_filters: settings.inloop_filters.into(),
decode_frame_type: settings.decode_frame_type.into(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct InloopFilters {
bits: u8,
}
impl InloopFilters {
pub const DEBLOCK: Self = Self {
bits: Rav1dInloopFilterType::DEBLOCK.bits(),
};
pub const CDEF: Self = Self {
bits: Rav1dInloopFilterType::CDEF.bits(),
};
pub const RESTORATION: Self = Self {
bits: Rav1dInloopFilterType::RESTORATION.bits(),
};
pub const fn all() -> Self {
Self {
bits: Rav1dInloopFilterType::DEBLOCK.bits()
| Rav1dInloopFilterType::CDEF.bits()
| Rav1dInloopFilterType::RESTORATION.bits(),
}
}
pub const fn none() -> Self {
Self { bits: 0 }
}
pub const fn contains(&self, other: Self) -> bool {
(self.bits & other.bits) == other.bits
}
pub const fn union(self, other: Self) -> Self {
Self {
bits: self.bits | other.bits,
}
}
}
impl From<InloopFilters> for Rav1dInloopFilterType {
fn from(filters: InloopFilters) -> Self {
Self::from_bits_retain(filters.bits)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DecodeFrameType {
All,
Reference,
Intra,
Key,
}
impl From<DecodeFrameType> for Rav1dDecodeFrameType {
fn from(ft: DecodeFrameType) -> Self {
match ft {
DecodeFrameType::All => Self::All,
DecodeFrameType::Reference => Self::Reference,
DecodeFrameType::Intra => Self::Intra,
DecodeFrameType::Key => Self::Key,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CpuLevel {
Scalar,
X86V2,
X86V3,
X86V4,
Neon,
NeonDotprod,
NeonI8mm,
Native,
}
impl CpuLevel {
pub const fn to_mask(self) -> u32 {
match self {
Self::Scalar => 0,
Self::X86V2 => {
(1 << 0) | (1 << 1) | (1 << 2) }
Self::X86V3 => {
(1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) }
Self::X86V4 => {
(1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) }
Self::Neon => 1 << 0,
Self::NeonDotprod => (1 << 0) | (1 << 1),
Self::NeonI8mm => (1 << 0) | (1 << 1) | (1 << 2),
Self::Native => u32::MAX,
}
}
pub fn platform_levels() -> &'static [CpuLevel] {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
&[
CpuLevel::Scalar,
CpuLevel::X86V2,
CpuLevel::X86V3,
CpuLevel::X86V4,
CpuLevel::Native,
]
}
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
{
&[
CpuLevel::Scalar,
CpuLevel::Neon,
CpuLevel::NeonDotprod,
CpuLevel::NeonI8mm,
CpuLevel::Native,
]
}
#[cfg(not(any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "arm",
target_arch = "aarch64",
)))]
{
&[CpuLevel::Scalar, CpuLevel::Native]
}
}
pub const fn name(self) -> &'static str {
match self {
Self::Scalar => "scalar",
Self::X86V2 => "x86-64-v2",
Self::X86V3 => "x86-64-v3",
Self::X86V4 => "x86-64-v4",
Self::Neon => "neon",
Self::NeonDotprod => "neon-dotprod",
Self::NeonI8mm => "neon-i8mm",
Self::Native => "native",
}
}
}
impl Default for CpuLevel {
fn default() -> Self {
Self::Native
}
}
impl std::fmt::Display for CpuLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
pub struct Decoder {
ctx: Arc<Rav1dContext>,
worker_handles: Vec<std::thread::JoinHandle<()>>,
}
pub const fn is_unchecked() -> bool {
cfg!(feature = "unchecked")
}
impl Decoder {
pub fn new() -> Result<Self> {
Self::with_settings(Settings::default())
}
pub fn with_settings(settings: Settings) -> Result<Self> {
crate::src::cpu::rav1d_set_cpu_flags_mask(settings.cpu_level.to_mask());
let rav1d_settings: Rav1dSettings = settings.into();
let (ctx, worker_handles) =
crate::src::lib::rav1d_open(&rav1d_settings).map_err(|_| Error::InitFailed)?;
Ok(Self {
ctx,
worker_handles,
})
}
pub fn decode(&mut self, data: &[u8]) -> Result<Option<Frame>> {
let mut rav1d_data = if !data.is_empty() {
let owned = data.to_vec().into_boxed_slice();
let cbox = crate::src::c_box::CBox::from_box(owned);
let carc = CArc::wrap(cbox).map_err(|_| Error::OutOfMemory)?;
Rav1dData {
data: Some(carc),
m: Default::default(),
}
} else {
Rav1dData {
data: None,
m: Default::default(),
}
};
crate::src::lib::rav1d_send_data(&self.ctx, &mut rav1d_data)?;
let mut pic = Rav1dPicture::default();
match crate::src::lib::rav1d_get_picture(&self.ctx, &mut pic) {
Ok(()) => Ok(Some(Frame { inner: pic })),
Err(Rav1dError::EAGAIN) => Ok(None),
Err(e) => Err(e.into()),
}
}
pub fn get_frame(&mut self) -> Result<Option<Frame>> {
let mut pic = Rav1dPicture::default();
match crate::src::lib::rav1d_get_picture(&self.ctx, &mut pic) {
Ok(()) => Ok(Some(Frame { inner: pic })),
Err(Rav1dError::EAGAIN) => Ok(None),
Err(e) => Err(e.into()),
}
}
pub fn flush(&mut self) -> Result<Vec<Frame>> {
crate::src::lib::rav1d_flush(&self.ctx);
let mut frames = Vec::new();
loop {
let mut pic = Rav1dPicture::default();
match crate::src::lib::rav1d_get_picture(&self.ctx, &mut pic) {
Ok(()) => frames.push(Frame { inner: pic }),
Err(Rav1dError::EAGAIN) => break,
Err(e) => return Err(e.into()),
}
}
Ok(frames)
}
}
impl Drop for Decoder {
fn drop(&mut self) {
self.ctx.tell_worker_threads_to_die();
for handle in self.worker_handles.drain(..) {
match handle.join() {
Ok(()) => {}
Err(e) => {
eprintln!("rav1d worker thread panicked during shutdown: {:?}", e);
}
}
}
}
}
#[derive(Clone)]
pub struct Frame {
inner: Rav1dPicture,
}
impl Frame {
pub fn width(&self) -> u32 {
self.inner.p.w as u32
}
pub fn height(&self) -> u32 {
self.inner.p.h as u32
}
pub fn bit_depth(&self) -> u8 {
self.inner.p.bpc
}
pub fn pixel_layout(&self) -> PixelLayout {
self.inner.p.layout.into()
}
pub fn planes(&self) -> Planes<'_> {
match self.bit_depth() {
8 => Planes::Depth8(Planes8 { frame: self }),
10 | 12 => Planes::Depth16(Planes16 { frame: self }),
_ => unreachable!("invalid bit depth: {}", self.bit_depth()),
}
}
pub fn color_info(&self) -> ColorInfo {
let seq_hdr = self.inner.seq_hdr.as_ref().expect("missing seq_hdr");
ColorInfo {
primaries: seq_hdr.rav1d.pri.into(),
transfer_characteristics: seq_hdr.rav1d.trc.into(),
matrix_coefficients: seq_hdr.rav1d.mtrx.into(),
color_range: if seq_hdr.rav1d.color_range != 0 {
ColorRange::Full
} else {
ColorRange::Limited
},
}
}
pub fn content_light(&self) -> Option<ContentLightLevel> {
self.inner
.content_light
.as_ref()
.map(|arc| ContentLightLevel {
max_content_light_level: arc.max_content_light_level,
max_frame_average_light_level: arc.max_frame_average_light_level,
})
}
pub fn mastering_display(&self) -> Option<MasteringDisplay> {
self.inner
.mastering_display
.as_ref()
.map(|arc| MasteringDisplay {
primaries: arc.primaries,
white_point: arc.white_point,
max_luminance: arc.max_luminance,
min_luminance: arc.min_luminance,
})
}
pub fn timestamp(&self) -> i64 {
self.inner.m.timestamp
}
pub fn duration(&self) -> i64 {
self.inner.m.duration
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PixelLayout {
I400,
I420,
I422,
I444,
}
impl From<Rav1dPixelLayout> for PixelLayout {
fn from(layout: Rav1dPixelLayout) -> Self {
match layout {
Rav1dPixelLayout::I400 => Self::I400,
Rav1dPixelLayout::I420 => Self::I420,
Rav1dPixelLayout::I422 => Self::I422,
Rav1dPixelLayout::I444 => Self::I444,
}
}
}
pub enum Planes<'a> {
Depth8(Planes8<'a>),
Depth16(Planes16<'a>),
}
pub struct Planes8<'a> {
frame: &'a Frame,
}
impl<'a> Planes8<'a> {
pub fn y(&self) -> PlaneView8<'a> {
let data = self
.frame
.inner
.data
.as_ref()
.expect("missing picture data");
let guard = data.data[0].slice::<BitDepth8, _>(..);
let stride = self.frame.inner.stride[0] as usize;
let buffer_height = if stride > 0 { guard.len() / stride } else { 0 };
let height = (self.frame.height() as usize).min(buffer_height);
PlaneView8 {
guard,
stride,
width: self.frame.width() as usize,
height,
}
}
pub fn u(&self) -> Option<PlaneView8<'a>> {
if self.frame.pixel_layout() == PixelLayout::I400 {
return None;
}
let (w, h) = self.chroma_dimensions();
let data = self
.frame
.inner
.data
.as_ref()
.expect("missing picture data");
let guard = data.data[1].slice::<BitDepth8, _>(..);
let stride = self.frame.inner.stride[1] as usize;
let buffer_height = if stride > 0 { guard.len() / stride } else { 0 };
let height = h.min(buffer_height);
Some(PlaneView8 {
guard,
stride,
width: w,
height,
})
}
pub fn v(&self) -> Option<PlaneView8<'a>> {
if self.frame.pixel_layout() == PixelLayout::I400 {
return None;
}
let (w, h) = self.chroma_dimensions();
let data = self
.frame
.inner
.data
.as_ref()
.expect("missing picture data");
let guard = data.data[2].slice::<BitDepth8, _>(..);
let stride = self.frame.inner.stride[1] as usize;
let buffer_height = if stride > 0 { guard.len() / stride } else { 0 };
let height = h.min(buffer_height);
Some(PlaneView8 {
guard,
stride,
width: w,
height,
})
}
fn chroma_dimensions(&self) -> (usize, usize) {
let w = self.frame.width() as usize;
let h = self.frame.height() as usize;
match self.frame.pixel_layout() {
PixelLayout::I420 => ((w + 1) / 2, (h + 1) / 2),
PixelLayout::I422 => ((w + 1) / 2, h),
PixelLayout::I444 => (w, h),
PixelLayout::I400 => (0, 0),
}
}
}
pub struct Planes16<'a> {
frame: &'a Frame,
}
impl<'a> Planes16<'a> {
pub fn y(&self) -> PlaneView16<'a> {
let data = self
.frame
.inner
.data
.as_ref()
.expect("missing picture data");
let guard = data.data[0].slice::<BitDepth16, _>(..);
let stride = self.frame.inner.stride[0] as usize / 2;
let buffer_height = if stride > 0 { guard.len() / stride } else { 0 };
let height = (self.frame.height() as usize).min(buffer_height);
PlaneView16 {
guard,
stride,
width: self.frame.width() as usize,
height,
}
}
pub fn u(&self) -> Option<PlaneView16<'a>> {
if self.frame.pixel_layout() == PixelLayout::I400 {
return None;
}
let (w, h) = self.chroma_dimensions();
let data = self
.frame
.inner
.data
.as_ref()
.expect("missing picture data");
let guard = data.data[1].slice::<BitDepth16, _>(..);
let stride = self.frame.inner.stride[1] as usize / 2;
let buffer_height = if stride > 0 { guard.len() / stride } else { 0 };
let height = h.min(buffer_height);
Some(PlaneView16 {
guard,
stride,
width: w,
height,
})
}
pub fn v(&self) -> Option<PlaneView16<'a>> {
if self.frame.pixel_layout() == PixelLayout::I400 {
return None;
}
let (w, h) = self.chroma_dimensions();
let data = self
.frame
.inner
.data
.as_ref()
.expect("missing picture data");
let guard = data.data[2].slice::<BitDepth16, _>(..);
let stride = self.frame.inner.stride[1] as usize / 2;
let buffer_height = if stride > 0 { guard.len() / stride } else { 0 };
let height = h.min(buffer_height);
Some(PlaneView16 {
guard,
stride,
width: w,
height,
})
}
fn chroma_dimensions(&self) -> (usize, usize) {
let w = self.frame.width() as usize;
let h = self.frame.height() as usize;
match self.frame.pixel_layout() {
PixelLayout::I420 => ((w + 1) / 2, (h + 1) / 2),
PixelLayout::I422 => ((w + 1) / 2, h),
PixelLayout::I444 => (w, h),
PixelLayout::I400 => (0, 0),
}
}
}
pub struct PlaneView8<'a> {
guard: DisjointImmutGuard<'a, Rav1dPictureDataComponentInner, [u8]>,
stride: usize,
width: usize,
height: usize,
}
impl<'a> PlaneView8<'a> {
pub fn row(&self, y: usize) -> &[u8] {
assert!(
y < self.height,
"row index {} out of bounds (height: {})",
y,
self.height
);
let start = y * self.stride;
&self.guard[start..start + self.width]
}
pub fn pixel(&self, x: usize, y: usize) -> u8 {
assert!(
x < self.width && y < self.height,
"pixel coordinates ({}, {}) out of bounds ({}x{})",
x,
y,
self.width,
self.height
);
self.guard[y * self.stride + x]
}
pub fn rows(&'a self) -> impl Iterator<Item = &'a [u8]> + 'a {
(0..self.height).map(move |y| self.row(y))
}
pub fn as_slice(&self) -> &[u8] {
self.guard.deref()
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn stride(&self) -> usize {
self.stride
}
}
pub struct PlaneView16<'a> {
guard: DisjointImmutGuard<'a, Rav1dPictureDataComponentInner, [u16]>,
stride: usize,
width: usize,
height: usize,
}
impl<'a> PlaneView16<'a> {
pub fn row(&self, y: usize) -> &[u16] {
assert!(
y < self.height,
"row index {} out of bounds (height: {})",
y,
self.height
);
let start = y * self.stride;
&self.guard[start..start + self.width]
}
pub fn pixel(&self, x: usize, y: usize) -> u16 {
assert!(
x < self.width && y < self.height,
"pixel coordinates ({}, {}) out of bounds ({}x{})",
x,
y,
self.width,
self.height
);
self.guard[y * self.stride + x]
}
pub fn rows(&'a self) -> impl Iterator<Item = &'a [u16]> + 'a {
(0..self.height).map(move |y| self.row(y))
}
pub fn as_slice(&self) -> &[u16] {
self.guard.deref()
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn stride(&self) -> usize {
self.stride
}
}
#[derive(Clone, Copy, Debug)]
pub struct ColorInfo {
pub primaries: ColorPrimaries,
pub transfer_characteristics: TransferCharacteristics,
pub matrix_coefficients: MatrixCoefficients,
pub color_range: ColorRange,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum ColorPrimaries {
Unknown = 0,
BT709 = 1,
Unspecified = 2,
BT470M = 4,
BT470BG = 5,
BT601 = 6,
SMPTE240 = 7,
Film = 8,
BT2020 = 9,
XYZ = 10,
SMPTE431 = 11,
SMPTE432 = 12,
EBU3213 = 22,
}
impl From<Rav1dColorPrimaries> for ColorPrimaries {
fn from(pri: Rav1dColorPrimaries) -> Self {
match pri.0 {
1 => Self::BT709,
4 => Self::BT470M,
5 => Self::BT470BG,
6 => Self::BT601,
7 => Self::SMPTE240,
8 => Self::Film,
9 => Self::BT2020,
10 => Self::XYZ,
11 => Self::SMPTE431,
12 => Self::SMPTE432,
22 => Self::EBU3213,
_ => Self::Unspecified,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TransferCharacteristics {
Reserved = 0,
BT709 = 1,
Unspecified = 2,
BT470M = 4,
BT470BG = 5,
BT601 = 6,
SMPTE240 = 7,
Linear = 8,
Log100 = 9,
Log100Sqrt10 = 10,
IEC61966 = 11,
BT1361 = 12,
SRGB = 13,
BT2020_10bit = 14,
BT2020_12bit = 15,
SMPTE2084 = 16,
SMPTE428 = 17,
HLG = 18,
}
impl From<Rav1dTransferCharacteristics> for TransferCharacteristics {
fn from(trc: Rav1dTransferCharacteristics) -> Self {
match trc.0 {
1 => Self::BT709,
4 => Self::BT470M,
5 => Self::BT470BG,
6 => Self::BT601,
7 => Self::SMPTE240,
8 => Self::Linear,
9 => Self::Log100,
10 => Self::Log100Sqrt10,
11 => Self::IEC61966,
12 => Self::BT1361,
13 => Self::SRGB,
14 => Self::BT2020_10bit,
15 => Self::BT2020_12bit,
16 => Self::SMPTE2084,
17 => Self::SMPTE428,
18 => Self::HLG,
_ => Self::Unspecified,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum MatrixCoefficients {
Identity = 0,
BT709 = 1,
Unspecified = 2,
Reserved = 3,
FCC = 4,
BT470BG = 5,
BT601 = 6,
SMPTE240 = 7,
YCgCo = 8,
BT2020NCL = 9,
BT2020CL = 10,
SMPTE2085 = 11,
ChromaDerivedNCL = 12,
ChromaDerivedCL = 13,
ICtCp = 14,
}
impl From<Rav1dMatrixCoefficients> for MatrixCoefficients {
fn from(mtrx: Rav1dMatrixCoefficients) -> Self {
match mtrx.0 {
0 => Self::Identity,
1 => Self::BT709,
4 => Self::FCC,
5 => Self::BT470BG,
6 => Self::BT601,
7 => Self::SMPTE240,
8 => Self::YCgCo,
9 => Self::BT2020NCL,
10 => Self::BT2020CL,
11 => Self::SMPTE2085,
12 => Self::ChromaDerivedNCL,
13 => Self::ChromaDerivedCL,
14 => Self::ICtCp,
_ => Self::Unspecified,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ColorRange {
Limited,
Full,
}
#[derive(Clone, Copy, Debug)]
pub struct ContentLightLevel {
pub max_content_light_level: u16,
pub max_frame_average_light_level: u16,
}
#[derive(Clone, Copy, Debug)]
pub struct MasteringDisplay {
pub primaries: [[u16; 2]; 3],
pub white_point: [u16; 2],
pub max_luminance: u32,
pub min_luminance: u32,
}
impl MasteringDisplay {
pub fn max_luminance_nits(&self) -> f64 {
self.max_luminance as f64 / 10000.0
}
pub fn min_luminance_nits(&self) -> f64 {
self.min_luminance as f64 / 10000.0
}
pub fn primary_chromaticity(&self, index: usize) -> [f64; 2] {
assert!(index < 3, "primary index must be 0-2");
[
self.primaries[index][0] as f64 / 50000.0,
self.primaries[index][1] as f64 / 50000.0,
]
}
pub fn white_point_chromaticity(&self) -> [f64; 2] {
[
self.white_point[0] as f64 / 50000.0,
self.white_point[1] as f64 / 50000.0,
]
}
}
pub fn enabled_features() -> String {
let mut features = Vec::new();
if cfg!(feature = "asm") {
features.push("asm");
}
if cfg!(feature = "partial_asm") {
features.push("partial_asm");
}
if cfg!(feature = "c-ffi") {
features.push("c-ffi");
}
if cfg!(feature = "unchecked") {
features.push("unchecked");
}
if cfg!(feature = "bitdepth_8") {
features.push("bitdepth_8");
}
if cfg!(feature = "bitdepth_16") {
features.push("bitdepth_16");
}
if cfg!(feature = "asm") {
features.push("safety:asm");
} else if cfg!(feature = "partial_asm") {
features.push("safety:partial-asm");
} else if cfg!(feature = "c-ffi") {
features.push("safety:c-ffi");
} else if cfg!(feature = "unchecked") {
features.push("safety:unchecked");
} else {
features.push("safety:forbid-unsafe");
}
features.join(", ")
}