use std::ptr::{self, addr_of_mut};
use std::slice;
use std::sync::LazyLock;
use hdf5_sys::h5p::{H5Pget_chunk, H5Pget_filter_by_id2, H5Pmodify_filter};
use hdf5_sys::h5t::{H5Tclose, H5Tget_class, H5Tget_size, H5Tget_super, H5T_FLOAT};
use hdf5_sys::h5z::{H5Z_class2_t, H5Z_filter_t, H5Zregister, H5Z_CLASS_T_VERS, H5Z_FLAG_REVERSE};
use crate::error::H5ErrorCode;
use crate::globals::{H5E_CALLBACK, H5E_PLIST};
use crate::internal_prelude::*;
use zfp_sys::zfp_stream;
pub use zfp_sys::{
bitstream, stream_close, stream_open, zfp_codec_version, zfp_compress, zfp_decompress,
zfp_field, zfp_field_1d, zfp_field_2d, zfp_field_3d, zfp_field_4d, zfp_field_alloc,
zfp_field_dimensionality, zfp_field_free, zfp_field_metadata, zfp_field_size, zfp_field_type,
zfp_library_version, zfp_mode, zfp_mode_zfp_mode_fixed_accuracy,
zfp_mode_zfp_mode_fixed_precision, zfp_mode_zfp_mode_fixed_rate, zfp_read_header,
zfp_stream_accuracy, zfp_stream_close, zfp_stream_compression_mode, zfp_stream_flush,
zfp_stream_maximum_size, zfp_stream_open, zfp_stream_precision, zfp_stream_rate,
zfp_stream_rewind, zfp_stream_set_accuracy, zfp_stream_set_bit_stream,
zfp_stream_set_precision, zfp_stream_set_rate, zfp_stream_set_reversible, zfp_type,
zfp_type_zfp_type_double, zfp_type_zfp_type_float, zfp_write_header, ZFP_HEADER_FULL,
ZFP_HEADER_MAGIC, ZFP_HEADER_MAX_BITS, ZFP_HEADER_META, ZFP_HEADER_MODE, ZFP_VERSION_MAJOR,
ZFP_VERSION_MINOR, ZFP_VERSION_PATCH, ZFP_VERSION_TWEAK,
};
use crate::filters::ZfpMode;
pub(crate) const MAX_NDIMS: usize = 4;
const ZFP_FILTER_NAME: &[u8] = b"zfp\0";
pub const ZFP_FILTER_ID: H5Z_filter_t = 32013;
const ZFP_FILTER_VERSION: c_uint = 1;
const ZFP_MODE_RATE: c_uint = 2;
const ZFP_MODE_PRECISION: c_uint = 3;
const ZFP_MODE_ACCURACY: c_uint = 4;
const ZFP_MODE_REVERSIBLE: c_uint = 5;
const ZFP_MODE_EXPERT: c_uint = 1;
const ZFP_FILTER_INFO: &H5Z_class2_t = &H5Z_class2_t {
version: H5Z_CLASS_T_VERS as _,
id: ZFP_FILTER_ID,
encoder_present: 1,
decoder_present: 1,
name: ZFP_FILTER_NAME.as_ptr().cast(),
can_apply: Some(can_apply_zfp),
set_local: Some(set_local_zfp),
filter: Some(filter_zfp),
};
static ZFP_INIT: LazyLock<Result<(), &'static str>> = LazyLock::new(|| {
let ret = unsafe { H5Zregister((ZFP_FILTER_INFO as *const H5Z_class2_t).cast()) };
if H5ErrorCode::is_err_code(ret) {
return Err("Can't register ZFP filter");
}
Ok(())
});
pub fn register_zfp() -> Result<(), &'static str> {
*ZFP_INIT
}
extern "C" fn can_apply_zfp(_dcpl_id: hid_t, type_id: hid_t, _space_id: hid_t) -> i32 {
let type_class = unsafe { H5Tget_class(type_id) };
if type_class == H5T_FLOAT {
1
} else {
0
}
}
extern "C" fn set_local_zfp(dcpl_id: hid_t, type_id: hid_t, _space_id: hid_t) -> herr_t {
const MAX_NDIMS: usize = 4;
let mut flags: c_uint = 0;
let mut nelmts: size_t = 4;
let mut values: Vec<c_uint> = vec![0; 4];
let ret = unsafe {
H5Pget_filter_by_id2(
dcpl_id,
ZFP_FILTER_ID,
addr_of_mut!(flags),
addr_of_mut!(nelmts),
values.as_mut_ptr(),
0,
ptr::null_mut(),
ptr::null_mut(),
)
};
if ret < 0 {
return -1;
}
let orig = values.clone();
nelmts = nelmts.max(10);
values.resize(nelmts as usize, 0);
values[0] = ZFP_FILTER_VERSION;
let mut chunkdims: Vec<hsize_t> = vec![0; MAX_NDIMS];
let ndims: c_int = unsafe { H5Pget_chunk(dcpl_id, MAX_NDIMS as _, chunkdims.as_mut_ptr()) };
if ndims < 0 {
return -1;
}
if ndims > MAX_NDIMS as _ {
h5err!("ZFP supports up to 4 dimensions", H5E_PLIST, H5E_CALLBACK);
return -1;
}
let typesize: size_t = unsafe { H5Tget_size(type_id) };
if typesize == 0 {
return -1;
}
values[1] = ndims as c_uint;
values[2] = typesize as c_uint;
for i in 0..(ndims as usize).min(values.len().saturating_sub(3)) {
values[i + 3] = chunkdims[i] as c_uint;
}
if values.len() >= 10 {
values[7] = orig.get(0).copied().unwrap_or(0);
values[8] = orig.get(1).copied().unwrap_or(0);
values[9] = orig.get(2).copied().unwrap_or(0);
}
let nelmts = 4;
let r = unsafe { H5Pmodify_filter(dcpl_id, ZFP_FILTER_ID, flags, nelmts, orig.as_ptr()) };
if r < 0 {
-1
} else {
1
}
}
const H5Z_ZFP_CD_NELMTS_MAX: usize = 8;
pub unsafe fn compute_hdr_cd_values(
zt: zfp_type,
ndims_used: usize,
dims_used: &[u64],
mode: ZfpMode, ) -> (Vec<u32>, usize) {
let dummy_field: *mut zfp_field = match ndims_used {
1 => zfp_field_1d(ptr::null_mut(), zt, dims_used[0].try_into().unwrap()),
2 => zfp_field_2d(
ptr::null_mut(),
zt,
dims_used[1].try_into().unwrap(),
dims_used[0].try_into().unwrap(),
),
3 => zfp_field_3d(
ptr::null_mut(),
zt,
dims_used[2].try_into().unwrap(),
dims_used[1].try_into().unwrap(),
dims_used[0].try_into().unwrap(),
),
4 => zfp_field_4d(
ptr::null_mut(),
zt,
dims_used[3].try_into().unwrap(),
dims_used[2].try_into().unwrap(),
dims_used[1].try_into().unwrap(),
dims_used[0].try_into().unwrap(),
),
_ => panic!("ZFP supports 1..4 non-unity dims"),
};
assert!(!dummy_field.is_null());
let mut hdr_cd_values = vec![0u32; H5Z_ZFP_CD_NELMTS_MAX];
hdr_cd_values[0] = make_version_word();
let ptr_bytes = hdr_cd_values[1..].as_mut_ptr() as *mut c_void;
let bytes_len = (hdr_cd_values.len() - 1) * std::mem::size_of::<u32>();
let dummy_bstr: *mut bitstream = stream_open(ptr_bytes, bytes_len as usize);
let dummy_zstr: *mut zfp_stream = zfp_stream_open(dummy_bstr);
match mode {
ZfpMode::Reversible => {
zfp_stream_set_reversible(dummy_zstr);
}
ZfpMode::FixedAccuracy(acc) => {
zfp_stream_set_accuracy(dummy_zstr, acc);
}
ZfpMode::FixedRate(rate) => {
zfp_stream_set_rate(dummy_zstr, rate, zt, ndims_used as u32, 0);
}
ZfpMode::FixedPrecision(precision) => {
zfp_stream_set_precision(dummy_zstr, precision as u32);
}
_ => unimplemented!(),
}
let hdr_bits = zfp_write_header(dummy_zstr, dummy_field, ZFP_HEADER_FULL as u32);
assert!(hdr_bits != 0);
zfp_stream_flush(dummy_zstr);
zfp_stream_close(dummy_zstr);
stream_close(dummy_bstr);
zfp_field_free(dummy_field);
let hdr_bytes = 1 + ((hdr_bits - 1) / 8);
let mut hdr_cd_nelmts = 1 + ((hdr_bytes - 1) / std::mem::size_of::<u32>());
hdr_cd_nelmts += 1;
(hdr_cd_values, hdr_cd_nelmts)
}
unsafe fn make_version_word() -> u32 {
const ZFP_VERSION_NO: u32 = (ZFP_VERSION_MAJOR << 12)
| (ZFP_VERSION_MINOR << 8)
| (ZFP_VERSION_PATCH << 4)
| (ZFP_VERSION_TWEAK);
const ZFP_CODEC: u32 = ZFP_VERSION_MINOR;
const H5Z_FILTER_ZFP_VERSION_MAJOR: u32 = 1;
const H5Z_FILTER_ZFP_VERSION_MINOR: u32 = 1;
const H5Z_FILTER_ZFP_VERSION_PATCH: u32 = 0;
const H5Z_FILTER_ZFP_VERSION_NO: u32 = (H5Z_FILTER_ZFP_VERSION_MAJOR << 8)
| (H5Z_FILTER_ZFP_VERSION_MINOR << 4)
| (H5Z_FILTER_ZFP_VERSION_PATCH);
(ZFP_VERSION_NO << 16) | (ZFP_CODEC << 12) | H5Z_FILTER_ZFP_VERSION_NO
}
#[derive(Debug)]
struct ZfpConfig {
pub ndims: c_int,
pub typesize: size_t,
pub dims: [size_t; 4],
pub mode: c_uint,
pub rate: f64,
pub precision: u32,
pub accuracy: f64,
}
pub unsafe fn parse_zfp_cdata(cd_nelmts: usize, cd_values: *const c_uint) -> Option<ZfpConfig> {
if cd_nelmts < 2 || cd_values.is_null() {
return None;
}
let cdata: &[u32] = slice::from_raw_parts(cd_values, cd_nelmts);
let _version_word = cdata[0];
let header_words = &cdata[1..];
if header_words.is_empty() {
return None;
}
let mut header_copy: Vec<u32> = header_words.to_vec();
let header_bytes = header_copy.len() * std::mem::size_of::<u32>();
let bstr: *mut bitstream = stream_open(header_copy.as_mut_ptr() as *mut c_void, header_bytes);
if bstr.is_null() {
return None;
}
let zstr: *mut zfp_stream = zfp_stream_open(bstr);
if zstr.is_null() {
stream_close(bstr);
return None;
}
let zfld: *mut zfp_field = zfp_field_alloc();
if zfld.is_null() {
zfp_stream_close(zstr);
stream_close(bstr);
return None;
}
let mut bits = zfp_read_header(zstr, zfld, ZFP_HEADER_MAGIC);
if bits == 0 {
for w in &mut header_copy {
*w = w.swap_bytes();
}
zfp_stream_rewind(zstr);
bits = zfp_read_header(zstr, zfld, ZFP_HEADER_MAGIC);
if bits == 0 {
zfp_field_free(zfld);
zfp_stream_close(zstr);
stream_close(bstr);
return None;
}
}
zfp_stream_rewind(zstr);
if zfp_read_header(zstr, zfld, ZFP_HEADER_FULL) == 0 {
zfp_field_free(zfld);
zfp_stream_close(zstr);
stream_close(bstr);
return None;
}
let ndims = zfp_field_dimensionality(zfld) as i32;
let mut size_per_dim: [usize; 4] = [0; 4];
if ndims > 0 {
zfp_field_size(zfld, size_per_dim.as_mut_ptr() as *mut _);
}
let mut dims: [usize; 4] = [0; 4];
for i in 0..(ndims as usize).min(4) {
dims[i] = size_per_dim[i];
}
let zt: zfp_type = zfp_field_type(zfld);
let typesize: usize = match zt {
x if x == zfp_sys::zfp_type_zfp_type_int32 => std::mem::size_of::<i32>(),
x if x == zfp_sys::zfp_type_zfp_type_int64 => std::mem::size_of::<i64>(),
x if x == zfp_sys::zfp_type_zfp_type_float => std::mem::size_of::<f32>(),
x if x == zfp_sys::zfp_type_zfp_type_double => std::mem::size_of::<f64>(),
_ => {
zfp_field_free(zfld);
zfp_stream_close(zstr);
stream_close(bstr);
return None;
}
};
let zmode_enum: zfp_mode = zfp_stream_compression_mode(zstr);
let mode = zmode_enum as u32;
let mut rate: f64 = 0.0;
let mut precision: u32 = 0;
let mut accuracy: f64 = 0.0;
match zmode_enum {
m if m == zfp_sys::zfp_mode_zfp_mode_fixed_rate => {
rate = zfp_stream_rate(zstr, ndims as u32);
}
m if m == zfp_sys::zfp_mode_zfp_mode_fixed_precision => {
precision = zfp_stream_precision(zstr);
}
m if m == zfp_sys::zfp_mode_zfp_mode_fixed_accuracy => {
accuracy = zfp_stream_accuracy(zstr);
}
m if m == zfp_sys::zfp_mode_zfp_mode_reversible => {
}
_ => {}
}
zfp_field_free(zfld);
zfp_stream_close(zstr);
stream_close(bstr);
Some(ZfpConfig { ndims, typesize, dims, mode, rate, precision, accuracy })
}
unsafe extern "C" fn filter_zfp(
flags: c_uint, cd_nelmts: size_t, cd_values: *const c_uint, nbytes: size_t,
buf_size: *mut size_t, buf: *mut *mut c_void,
) -> size_t {
let cfg = if let Some(cfg) = parse_zfp_cdata(cd_nelmts, cd_values) {
cfg
} else {
return 0;
};
if flags & H5Z_FLAG_REVERSE == 0 {
unsafe { filter_zfp_compress(&cfg, buf_size, buf) }
} else {
unsafe { filter_zfp_decompress(&cfg, nbytes, buf_size, buf) }
}
}
unsafe fn filter_zfp_compress(
cfg: &ZfpConfig, buf_size: *mut size_t, buf: *mut *mut c_void,
) -> size_t {
let zfp_stream = zfp_stream_open(ptr::null_mut());
if zfp_stream.is_null() {
h5err!("Failed to open ZFP stream", H5E_PLIST, H5E_CALLBACK);
return 0;
}
match cfg.mode {
ZFP_MODE_RATE => {
zfp_stream_set_rate(zfp_stream, cfg.rate, cfg.typesize as _, cfg.ndims as _, 0);
}
ZFP_MODE_PRECISION => {
zfp_stream_set_precision(zfp_stream, cfg.precision);
}
ZFP_MODE_ACCURACY => {
zfp_stream_set_accuracy(zfp_stream, cfg.accuracy);
}
ZFP_MODE_REVERSIBLE => zfp_stream_set_reversible(zfp_stream),
_ => {
zfp_stream_close(zfp_stream);
return 0;
}
}
let field = if cfg.typesize == 4 {
match cfg.ndims {
1 => zfp_field_1d((*buf).cast(), zfp_type_zfp_type_float, cfg.dims[0]),
2 => zfp_field_2d((*buf).cast(), zfp_type_zfp_type_float, cfg.dims[0], cfg.dims[1]),
3 => zfp_field_3d(
(*buf).cast(),
zfp_type_zfp_type_float,
cfg.dims[0],
cfg.dims[1],
cfg.dims[2],
),
4 => zfp_field_4d(
(*buf).cast(),
zfp_type_zfp_type_float,
cfg.dims[0],
cfg.dims[1],
cfg.dims[2],
cfg.dims[3],
),
_ => ptr::null_mut(),
}
} else {
match cfg.ndims {
1 => zfp_field_1d((*buf).cast(), zfp_type_zfp_type_double, cfg.dims[0]),
2 => zfp_field_2d((*buf).cast(), zfp_type_zfp_type_double, cfg.dims[0], cfg.dims[1]),
3 => zfp_field_3d(
(*buf).cast(),
zfp_type_zfp_type_double,
cfg.dims[0],
cfg.dims[1],
cfg.dims[2],
),
4 => zfp_field_4d(
(*buf).cast(),
zfp_type_zfp_type_double,
cfg.dims[0],
cfg.dims[1],
cfg.dims[2],
cfg.dims[3],
),
_ => ptr::null_mut(),
}
};
if field.is_null() {
zfp_stream_close(zfp_stream);
h5err!("Failed to create ZFP field", H5E_PLIST, H5E_CALLBACK);
return 0;
}
let maxsize = zfp_stream_maximum_size(zfp_stream, field);
let outbuf = libc::malloc(maxsize);
if outbuf.is_null() {
zfp_field_free(field);
zfp_stream_close(zfp_stream);
h5err!("Can't allocate compression buffer", H5E_PLIST, H5E_CALLBACK);
return 0;
}
let bitstream = stream_open(outbuf.cast(), maxsize);
zfp_stream_set_bit_stream(zfp_stream, bitstream);
zfp_stream_rewind(zfp_stream);
let compressed_size = zfp_compress(zfp_stream, field);
stream_close(bitstream);
zfp_field_free(field);
zfp_stream_close(zfp_stream);
if compressed_size == 0 {
libc::free(outbuf);
h5err!("ZFP compression failed", H5E_PLIST, H5E_CALLBACK);
return 0;
}
libc::free(*buf);
*buf = outbuf;
*buf_size = compressed_size;
compressed_size
}
unsafe fn filter_zfp_decompress(
cfg: &ZfpConfig, nbytes: size_t, buf_size: *mut size_t, buf: *mut *mut c_void,
) -> size_t {
let zfp_stream = zfp_stream_open(ptr::null_mut());
if zfp_stream.is_null() {
h5err!("Failed to open ZFP stream", H5E_PLIST, H5E_CALLBACK);
return 0;
}
match cfg.mode {
ZFP_MODE_RATE => {
zfp_stream_set_rate(zfp_stream, cfg.rate, cfg.typesize as _, cfg.ndims as _, 0);
}
ZFP_MODE_PRECISION => {
zfp_stream_set_precision(zfp_stream, cfg.precision);
}
ZFP_MODE_ACCURACY => {
zfp_stream_set_accuracy(zfp_stream, cfg.accuracy);
}
ZFP_MODE_REVERSIBLE => zfp_stream_set_reversible(zfp_stream),
_ => {
zfp_stream_close(zfp_stream);
return 0;
}
}
let mut outbuf_size = cfg.typesize;
for i in 0..cfg.ndims as usize {
outbuf_size *= cfg.dims[i];
}
let outbuf = libc::malloc(outbuf_size);
if outbuf.is_null() {
zfp_stream_close(zfp_stream);
h5err!("Can't allocate decompression buffer", H5E_PLIST, H5E_CALLBACK);
return 0;
}
let field = if cfg.typesize == 4 {
match cfg.ndims {
1 => zfp_field_1d(outbuf.cast(), zfp_type_zfp_type_float, cfg.dims[0]),
2 => zfp_field_2d(outbuf.cast(), zfp_type_zfp_type_float, cfg.dims[0], cfg.dims[1]),
3 => zfp_field_3d(
outbuf.cast(),
zfp_type_zfp_type_float,
cfg.dims[0],
cfg.dims[1],
cfg.dims[2],
),
4 => zfp_field_4d(
outbuf.cast(),
zfp_type_zfp_type_float,
cfg.dims[0],
cfg.dims[1],
cfg.dims[2],
cfg.dims[3],
),
_ => ptr::null_mut(),
}
} else {
match cfg.ndims {
1 => zfp_field_1d(outbuf.cast(), zfp_type_zfp_type_double, cfg.dims[0]),
2 => zfp_field_2d(outbuf.cast(), zfp_type_zfp_type_double, cfg.dims[0], cfg.dims[1]),
3 => zfp_field_3d(
outbuf.cast(),
zfp_type_zfp_type_double,
cfg.dims[0],
cfg.dims[1],
cfg.dims[2],
),
4 => zfp_field_4d(
outbuf.cast(),
zfp_type_zfp_type_double,
cfg.dims[0],
cfg.dims[1],
cfg.dims[2],
cfg.dims[3],
),
_ => ptr::null_mut(),
}
};
if field.is_null() {
libc::free(outbuf);
zfp_stream_close(zfp_stream);
h5err!("Failed to create ZFP field", H5E_PLIST, H5E_CALLBACK);
return 0;
}
let bitstream = stream_open((*buf).cast(), nbytes);
zfp_stream_set_bit_stream(zfp_stream, bitstream);
zfp_stream_rewind(zfp_stream);
let status = zfp_decompress(zfp_stream, field);
stream_close(bitstream);
zfp_field_free(field);
zfp_stream_close(zfp_stream);
if status == 0 {
libc::free(outbuf);
h5err!("ZFP decompression failed", H5E_PLIST, H5E_CALLBACK);
return 0;
}
libc::free(*buf);
*buf = outbuf;
*buf_size = outbuf_size;
outbuf_size
}