use std::{
num::NonZeroUsize,
sync::{Arc, OnceLock},
};
use jxr_core::{
DecodeReport, DecodeRequest, ImageInfo, PixelFormat, PreparedPlan, Rect, StorageKind,
SurfaceLayout,
};
use super::{BatchInfrastructureError, IndexedBatchError};
use crate::PreparedJxr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BatchDecodeOptions {
pub layout: BatchLayout,
pub workers: Option<NonZeroUsize>,
pub max_inputs: usize,
pub max_host_allocation_bytes: u64,
pub preparation_cache_entries: usize,
}
impl Default for BatchDecodeOptions {
fn default() -> Self {
Self {
layout: BatchLayout::Native,
workers: None,
max_inputs: 1 << 20,
max_host_allocation_bytes: 1 << 32,
preparation_cache_entries: 256,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum BatchLayout {
#[default]
Native,
Nchw,
}
#[derive(Debug, Clone)]
pub struct EncodedImage {
pub bytes: Arc<[u8]>,
pub request: DecodeRequest,
}
impl EncodedImage {
#[must_use]
pub fn new(bytes: Arc<[u8]>, request: DecodeRequest) -> Self {
Self { bytes, request }
}
#[must_use]
pub fn full(bytes: Arc<[u8]>, format: PixelFormat) -> Self {
Self::new(bytes, DecodeRequest::new(format))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BatchGroupInfo {
layout: SurfaceLayout,
batch_layout: BatchLayout,
}
impl BatchGroupInfo {
pub(crate) const fn new(layout: SurfaceLayout, batch_layout: BatchLayout) -> Self {
Self {
layout,
batch_layout,
}
}
#[must_use]
pub const fn dimensions(&self) -> (u32, u32) {
(self.layout.width, self.layout.height)
}
#[must_use]
pub const fn format(&self) -> PixelFormat {
self.layout.format
}
#[must_use]
pub const fn storage_kind(&self) -> StorageKind {
self.layout.format.storage_kind()
}
#[must_use]
pub const fn image_stride_bytes(&self) -> usize {
self.layout.byte_len
}
#[must_use]
pub const fn image_stride_elements(&self) -> usize {
let element_bytes = match self.storage_kind() {
StorageKind::BitPacked | StorageKind::U8 => 1,
StorageKind::U16 | StorageKind::I16 | StorageKind::F16Bits | StorageKind::PackedU16 => {
2
}
StorageKind::I32 | StorageKind::F32 | StorageKind::PackedU32 => 4,
};
self.layout.byte_len / element_bytes
}
#[must_use]
pub const fn image_layout(&self) -> &SurfaceLayout {
&self.layout
}
#[must_use]
pub const fn batch_layout(&self) -> BatchLayout {
self.batch_layout
}
}
#[derive(Clone, Debug)]
pub struct PreparedImage {
pub(crate) inner: Arc<PreparedImageInner>,
}
#[derive(Debug)]
pub(crate) struct PreparedImageInner {
pub(crate) contract: Arc<PreparedImageContract>,
pub(crate) original_source_index: usize,
}
#[derive(Debug)]
pub(crate) struct PreparedImageContract {
pub(crate) image: PreparedJxr,
pub(crate) request: DecodeRequest,
pub(crate) plan: PreparedPlan,
pub(crate) info: BatchGroupInfo,
pub(crate) reconstruction: OnceLock<Result<crate::PreparedReconstruction, jxr_core::JxrError>>,
}
impl PreparedImage {
#[must_use]
pub fn image(&self) -> &PreparedJxr {
&self.inner.contract.image
}
#[must_use]
pub fn request(&self) -> &DecodeRequest {
&self.inner.contract.request
}
#[must_use]
pub fn plan(&self) -> &PreparedPlan {
&self.inner.contract.plan
}
#[must_use]
pub fn info(&self) -> &BatchGroupInfo {
&self.inner.contract.info
}
#[must_use]
pub fn original_source_index(&self) -> usize {
self.inner.original_source_index
}
pub fn prepare_reconstruction(
&self,
) -> Result<crate::PreparedReconstruction, jxr_core::JxrError> {
self.inner
.contract
.reconstruction
.get_or_init(|| {
self.image()
.decoder()
.prepare_reconstruction_from_plan(self.request(), self.plan().clone())
})
.clone()
}
#[must_use]
pub fn reconstruction_is_cached(&self) -> bool {
self.inner.contract.reconstruction.get().is_some()
}
}
#[derive(Debug)]
pub struct PreparedBatchGroup {
pub(crate) info: BatchGroupInfo,
pub(crate) images: Vec<PreparedImage>,
pub(crate) source_indices: Vec<usize>,
}
impl PreparedBatchGroup {
#[must_use]
pub const fn info(&self) -> &BatchGroupInfo {
&self.info
}
#[must_use]
pub fn images(&self) -> &[PreparedImage] {
&self.images
}
#[must_use]
pub fn source_indices(&self) -> &[usize] {
&self.source_indices
}
}
#[derive(Clone, Debug)]
pub struct PreparedBatch {
pub(crate) groups: Arc<[PreparedBatchGroup]>,
pub(crate) errors: Arc<[IndexedBatchError]>,
pub(crate) options: BatchDecodeOptions,
}
impl PreparedBatch {
#[must_use]
pub fn input_count(&self) -> usize {
self.groups.iter().fold(self.errors.len(), |count, group| {
count.saturating_add(group.images.len())
})
}
#[must_use]
pub fn groups(&self) -> &[PreparedBatchGroup] {
&self.groups
}
#[must_use]
pub fn errors(&self) -> &[IndexedBatchError] {
&self.errors
}
#[must_use]
pub const fn options(&self) -> BatchDecodeOptions {
self.options
}
}
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub enum CpuBatchSamples {
BitPacked(Vec<u8>),
U8(Vec<u8>),
U16(Vec<u16>),
I16(Vec<i16>),
I32(Vec<i32>),
F16(Vec<u16>),
F32(Vec<f32>),
Rgb555(Vec<u16>),
Rgb565(Vec<u16>),
Rgb101010(Vec<u32>),
Rgbe(Vec<u32>),
}
pub type CpuBatchDestination<'a> = jxr_core::DecodedSamplesMut<'a>;
impl CpuBatchSamples {
#[must_use]
pub const fn storage_kind(&self) -> StorageKind {
match self {
Self::BitPacked(_) => StorageKind::BitPacked,
Self::U8(_) => StorageKind::U8,
Self::U16(_) => StorageKind::U16,
Self::I16(_) => StorageKind::I16,
Self::I32(_) => StorageKind::I32,
Self::F16(_) => StorageKind::F16Bits,
Self::F32(_) => StorageKind::F32,
Self::Rgb555(_) | Self::Rgb565(_) => StorageKind::PackedU16,
Self::Rgb101010(_) | Self::Rgbe(_) => StorageKind::PackedU32,
}
}
#[must_use]
pub fn len(&self) -> usize {
match self {
Self::BitPacked(values) | Self::U8(values) => values.len(),
Self::U16(values) | Self::F16(values) | Self::Rgb555(values) | Self::Rgb565(values) => {
values.len()
}
Self::I16(values) => values.len(),
Self::I32(values) => values.len(),
Self::F32(values) => values.len(),
Self::Rgb101010(values) | Self::Rgbe(values) => values.len(),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn byte_len(&self) -> usize {
match self {
Self::BitPacked(values) | Self::U8(values) => values.len(),
Self::U16(values) | Self::F16(values) | Self::Rgb555(values) | Self::Rgb565(values) => {
values.len().saturating_mul(2)
}
Self::I16(values) => values.len().saturating_mul(2),
Self::I32(values) => values.len().saturating_mul(4),
Self::F32(values) => values.len().saturating_mul(4),
Self::Rgb101010(values) | Self::Rgbe(values) => values.len().saturating_mul(4),
}
}
}
#[derive(Debug, PartialEq)]
pub struct CpuBatchGroup {
pub(crate) info: BatchGroupInfo,
pub(crate) source_indices: Vec<usize>,
pub(crate) image_infos: Vec<ImageInfo>,
pub(crate) decoded_regions: Vec<Rect>,
pub(crate) reports: Vec<DecodeReport>,
pub(crate) samples: CpuBatchSamples,
}
impl CpuBatchGroup {
#[must_use]
pub const fn info(&self) -> &BatchGroupInfo {
&self.info
}
#[must_use]
pub fn source_indices(&self) -> &[usize] {
&self.source_indices
}
#[must_use]
pub fn image_infos(&self) -> &[ImageInfo] {
&self.image_infos
}
#[must_use]
pub fn decoded_regions(&self) -> &[Rect] {
&self.decoded_regions
}
#[must_use]
pub fn reports(&self) -> &[DecodeReport] {
&self.reports
}
#[must_use]
pub const fn image_stride_bytes(&self) -> usize {
self.info.image_stride_bytes()
}
#[must_use]
pub const fn image_stride_elements(&self) -> usize {
self.info.image_stride_elements()
}
#[must_use]
pub const fn samples(&self) -> &CpuBatchSamples {
&self.samples
}
}
#[derive(Debug, PartialEq)]
pub struct CpuBatchDecodeResult {
pub(crate) groups: Vec<CpuBatchGroup>,
pub(crate) errors: Vec<IndexedBatchError>,
}
#[derive(Debug, PartialEq)]
pub struct CpuBatchIntoResult {
pub(crate) source_indices: Vec<usize>,
pub(crate) image_infos: Vec<ImageInfo>,
pub(crate) decoded_regions: Vec<Rect>,
pub(crate) reports: Vec<DecodeReport>,
pub(crate) errors: Vec<IndexedBatchError>,
}
impl CpuBatchIntoResult {
#[must_use]
pub fn source_indices(&self) -> &[usize] {
&self.source_indices
}
#[must_use]
pub fn image_infos(&self) -> &[ImageInfo] {
&self.image_infos
}
#[must_use]
pub fn decoded_regions(&self) -> &[Rect] {
&self.decoded_regions
}
#[must_use]
pub fn reports(&self) -> &[DecodeReport] {
&self.reports
}
#[must_use]
pub fn errors(&self) -> &[IndexedBatchError] {
&self.errors
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CpuBatchDiagnostics {
pub preparation_calls: u64,
pub prepared_inputs: u64,
pub preparation_cache_hits: u64,
pub preparation_cache_misses: u64,
pub decode_calls: u64,
pub direct_dense_images: u64,
pub fallback_materialized_images: u64,
pub coefficient_workspace_reuses: u64,
pub retained_coefficient_bytes: u64,
pub reconstruction_workspace_reuses: u64,
pub retained_reconstruction_bytes: u64,
pub layout_workspace_reuses: u64,
pub retained_layout_bytes: u64,
pub output_compaction_copied_samples: u64,
}
impl CpuBatchDecodeResult {
#[must_use]
pub fn groups(&self) -> &[CpuBatchGroup] {
&self.groups
}
#[must_use]
pub fn errors(&self) -> &[IndexedBatchError] {
&self.errors
}
}
pub trait BatchDecoder {
type Output;
type Error: From<BatchInfrastructureError>;
fn options(&self) -> BatchDecodeOptions;
fn prepare_batch(&self, inputs: Vec<EncodedImage>) -> Result<PreparedBatch, Self::Error>;
fn prepare_prepared_images(
&self,
images: Vec<PreparedImage>,
) -> Result<PreparedBatch, Self::Error>;
fn decode_prepared(&mut self, prepared: &PreparedBatch) -> Result<Self::Output, Self::Error>;
fn decode_batch(&mut self, inputs: Vec<EncodedImage>) -> Result<Self::Output, Self::Error> {
let prepared = self.prepare_batch(inputs)?;
self.decode_prepared(&prepared)
}
}