1use core::num::NonZeroUsize;
4
5use crate::{backend::BackendRequest, pixel::PixelFormat, scale::Downscale, types::Rect};
6
7mod allocation;
8mod collection;
9mod gpu_job_chunk;
10
11#[doc(hidden)]
12pub use allocation::{
13 checked_batch_count_product, checked_batch_count_sum, try_batch_reserve_for_push,
14 try_batch_reserve_to, BatchAllocationBudget, BatchAllocationRequest,
15};
16pub use collection::{
17 try_collect_indexed_batch_results, try_collect_ordered_batch_results_with_limits,
18};
19#[doc(hidden)]
20pub use gpu_job_chunk::{
21 plan_ht_gpu_job_chunks, HtGpuJobChunk, HtGpuJobChunkEntry, HtGpuJobChunkLimit,
22 HtGpuJobChunkLimits, HtGpuJobChunkPlan, HtGpuJobChunkPlanError, HtGpuJobChunkRequest,
23 HtGpuJobPassBucket,
24};
25
26#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
28pub struct TileBatchOptions {
29 pub workers: Option<NonZeroUsize>,
31}
32
33impl TileBatchOptions {
34 #[must_use]
36 pub const fn new(workers: Option<NonZeroUsize>) -> Self {
37 Self { workers }
38 }
39}
40
41#[doc(hidden)]
43pub type IndexedBatchResult<T, E> = (usize, Result<T, E>);
44
45#[doc(hidden)]
47pub type BatchResultSlot<T, E> = Option<Result<T, E>>;
48
49pub struct TileDecodeJob<'i, 'o> {
51 pub input: &'i [u8],
53 pub out: &'o mut [u8],
55 pub stride: usize,
57}
58
59pub struct TileRegionDecodeJob<'i, 'o> {
61 pub input: &'i [u8],
63 pub out: &'o mut [u8],
65 pub stride: usize,
67 pub roi: Rect,
69}
70
71pub struct TileScaledDecodeJob<'i, 'o> {
73 pub input: &'i [u8],
75 pub out: &'o mut [u8],
77 pub stride: usize,
79 pub scale: Downscale,
81}
82
83pub struct TileRegionScaledDecodeJob<'i, 'o> {
85 pub input: &'i [u8],
87 pub out: &'o mut [u8],
89 pub stride: usize,
91 pub roi: Rect,
93 pub scale: Downscale,
95}
96
97pub struct TileRegionScaledDeviceDecodeRequest<'i> {
99 pub input: &'i [u8],
101 pub fmt: PixelFormat,
103 pub roi: Rect,
105 pub scale: Downscale,
107 pub backend: BackendRequest,
109}
110
111#[derive(Debug)]
113pub struct TileBatchError<E> {
114 pub index: usize,
116 pub source: E,
118}
119
120impl<E: core::fmt::Display> core::fmt::Display for TileBatchError<E> {
121 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
122 write!(f, "tile {} decode failed: {}", self.index, self.source)
123 }
124}
125
126impl<E: core::error::Error + 'static> core::error::Error for TileBatchError<E> {
127 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
128 Some(&self.source)
129 }
130}
131
132#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
137#[non_exhaustive]
138pub enum BatchInfrastructureError {
139 #[error("batch plan requires at least one job")]
141 EmptyBatchPlan,
142 #[error("unsupported batch contract: {what}")]
144 UnsupportedContract {
145 what: &'static str,
147 },
148 #[error("{what} is too large: requested {requested} bytes, cap {cap}")]
150 AllocationTooLarge {
151 what: &'static str,
153 requested: usize,
155 cap: usize,
157 },
158 #[error("host allocation failed for {bytes} bytes while allocating {what}")]
160 HostAllocationFailed {
161 what: &'static str,
163 bytes: usize,
165 },
166 #[error("failed to spawn batch worker {worker}")]
168 WorkerSpawnFailed {
169 worker: usize,
171 },
172 #[error("batch worker {worker} panicked")]
174 WorkerPanicked {
175 worker: usize,
177 },
178 #[error("batch worker slot {worker} is outside retained slot count {available}")]
180 WorkerSlotMissing {
181 worker: usize,
183 available: usize,
185 },
186 #[error("parallel batch worker panicked")]
188 ParallelWorkerPanicked,
189 #[error("batch scheduler state was poisoned")]
191 SchedulerPoisoned,
192 #[error("batch result index {index} is outside job count {job_count}")]
194 ResultIndexOutOfBounds {
195 index: usize,
197 job_count: usize,
199 },
200 #[error("batch result index {index} was reported more than once")]
202 DuplicateResult {
203 index: usize,
205 },
206 #[error("batch worker result missing for job {index}")]
208 MissingResult {
209 index: usize,
211 },
212 #[error("batch result {index} changed kind during ordered collection")]
214 ResultKindMismatch {
215 index: usize,
217 },
218}
219
220#[derive(Debug)]
225#[non_exhaustive]
226pub enum BatchDecodeError<E> {
227 Tile(TileBatchError<E>),
229 Infrastructure(BatchInfrastructureError),
231}
232
233impl<E> BatchDecodeError<E> {
234 #[must_use]
236 pub const fn tile_error(&self) -> Option<&TileBatchError<E>> {
237 match self {
238 Self::Tile(error) => Some(error),
239 Self::Infrastructure(_) => None,
240 }
241 }
242
243 #[must_use]
245 pub const fn infrastructure_error(&self) -> Option<&BatchInfrastructureError> {
246 match self {
247 Self::Tile(_) => None,
248 Self::Infrastructure(error) => Some(error),
249 }
250 }
251}
252
253impl<E: core::fmt::Display> core::fmt::Display for BatchDecodeError<E> {
254 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
255 match self {
256 Self::Tile(error) => error.fmt(f),
257 Self::Infrastructure(error) => error.fmt(f),
258 }
259 }
260}
261
262impl<E: core::error::Error + 'static> core::error::Error for BatchDecodeError<E> {
263 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
264 match self {
265 Self::Tile(error) => Some(error),
266 Self::Infrastructure(error) => Some(error),
267 }
268 }
269}
270
271impl<E> From<BatchInfrastructureError> for BatchDecodeError<E> {
272 fn from(error: BatchInfrastructureError) -> Self {
273 Self::Infrastructure(error)
274 }
275}
276
277impl<E> From<TileBatchError<E>> for BatchDecodeError<E> {
278 fn from(error: TileBatchError<E>) -> Self {
279 Self::Tile(error)
280 }
281}
282
283#[doc(hidden)]
288pub fn tile_batch_worker_count(
289 batch_size: usize,
290 options: TileBatchOptions,
291 available_workers: usize,
292) -> usize {
293 if batch_size <= 1 {
294 return 1;
295 }
296 let workers = options.workers.map_or(available_workers, NonZeroUsize::get);
297 workers.max(1).min(batch_size)
298}