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
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::sync::Arc;
use j2k_core::DeviceSubmission;
use j2k_metal_support::FallibleSubmissionQueue;
use crate::{batch, Error, MetalDecodeRequest, MetalSession, Surface};
/// Convenience wrapper for submitting a group of J2K/HTJ2K tiles to one
/// decoder session.
///
/// This is intentionally codec-scoped: callers own slide metadata, tile
/// coordinates, cache policy, and viewport decisions. The batch only preserves
/// submission order and lets compatible tile requests share the Metal session.
#[derive(Default)]
pub struct MetalTileBatch {
session: MetalSession,
queue: FallibleSubmissionQueue<batch::MetalSubmission>,
}
impl MetalTileBatch {
/// Create an empty tile batch.
pub fn new() -> Self {
Self::default()
}
/// Create an empty tile batch with capacity for `capacity` submissions.
pub fn with_capacity(capacity: usize) -> Self {
// Capacity is a hint only: reserving is deferred to the fallible push
// boundary because this constructor cannot report allocation failure.
Self {
queue: FallibleSubmissionQueue::with_capacity_hint(capacity),
..Self::default()
}
}
/// Number of queued tile requests.
pub fn len(&self) -> usize {
self.queue.len()
}
/// Whether the batch has no queued tile requests.
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
/// Number of Metal session submissions already flushed.
///
/// Queued requests normally do not increment this until `decode_all` waits
/// on the first result.
pub fn submissions(&self) -> Result<u64, Error> {
self.session.submissions()
}
/// Queue a tile decode request, copying the compressed tile bytes into the batch.
pub fn push_tile_request(
&mut self,
input: &[u8],
request: MetalDecodeRequest,
) -> Result<usize, Error> {
self.push_shared_tile_request(Arc::<[u8]>::from(input), request)
}
/// Queue a tile decode request backed by shared compressed tile bytes.
pub fn push_shared_tile_request(
&mut self,
input: Arc<[u8]>,
request: MetalDecodeRequest,
) -> Result<usize, Error> {
let Self { session, queue } = self;
queue.try_push_with(
"J2K Metal tile batch submissions",
|_, retained_capacity| {
batch::queue_tile_request_shared_with_retained(
session,
input,
request.fmt,
request.backend,
request.op.batch_op(),
retained_capacity,
)
},
)
}
/// Decode all queued tile requests and return surfaces in submission order.
pub fn decode_all(self) -> Result<Vec<Surface>, Error> {
self.queue.try_finish(
"J2K Metal tile batch surface collection",
"J2K Metal tile batch surface results",
DeviceSubmission::wait,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use j2k_core::{BackendRequest, Downscale, PixelFormat, Rect};
#[test]
fn push_tile_request_preserves_submission_slots() {
let mut batch = MetalTileBatch::with_capacity(2);
let bytes = [0xff, 0x4f, 0xff, 0x51];
let roi = Rect {
x: 0,
y: 0,
w: 1,
h: 1,
};
let first = batch
.push_tile_request(
&bytes,
MetalDecodeRequest::full(PixelFormat::Gray8, BackendRequest::Cpu),
)
.expect("queue full tile");
let second = batch
.push_shared_tile_request(
Arc::<[u8]>::from(&bytes[..]),
MetalDecodeRequest::region_scaled(
PixelFormat::Gray8,
roi,
Downscale::Half,
BackendRequest::Cpu,
),
)
.expect("queue region-scaled tile");
assert_eq!(first, 0);
assert_eq!(second, 1);
assert_eq!(batch.len(), 2);
assert!(!batch.is_empty());
}
#[test]
fn oversized_capacity_hint_fails_before_queue_mutation() {
let mut batch = MetalTileBatch::with_capacity(usize::MAX);
let error = batch
.push_tile_request(
&[0xff, 0x4f],
MetalDecodeRequest::full(PixelFormat::Gray8, BackendRequest::Cpu),
)
.expect_err("oversized capacity hint");
assert!(matches!(
error,
Error::BatchInfrastructure(j2k_core::BatchInfrastructureError::AllocationTooLarge {
what: "J2K Metal tile batch submissions",
requested: usize::MAX,
cap: j2k_core::DEFAULT_MAX_HOST_ALLOCATION_BYTES,
})
));
assert!(batch.is_empty());
}
}