#[cfg(test)]
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{
collections::{hash_map::DefaultHasher, HashMap},
hash::{Hash, Hasher},
sync::{Arc, Mutex, OnceLock},
time::Instant,
};
use j2k::J2kError;
use j2k_core::{Downscale, PixelFormat, Rect};
use j2k_native::{
DecodeSettings as NativeDecodeSettings, DecoderContext as NativeDecoderContext,
Image as NativeImage,
};
use metal::Device;
use rayon::prelude::*;
use crate::{direct, Error, J2kDecoder, Surface};
pub(crate) const RGB_REGION_SCALED_METAL_DIRECT_UNSUPPORTED: &str =
"J2K Metal ROI+scaled hybrid decode currently supports single-tile RGB direct plans for Rgb8/Rgba8/Rgb16";
const METAL_PROFILE_DECODE_LABEL_ENV: &str = "J2K_METAL_PROFILE_DECODE_LABEL";
const REGION_SCALED_COLOR_PLAN_CACHE_CAP: usize = 128;
static REGION_SCALED_COLOR_PLAN_CACHE: OnceLock<
Mutex<HashMap<u64, Arc<crate::compute::PreparedDirectColorPlan>>>,
> = OnceLock::new();
#[cfg(test)]
macro_rules! test_atomic_counter {
($counter:ident, $reset:ident, $load:ident) => {
static $counter: AtomicUsize = AtomicUsize::new(0);
pub(crate) fn $reset() {
$counter.store(0, Ordering::Relaxed);
}
pub(crate) fn $load() -> usize {
$counter.load(Ordering::Relaxed)
}
};
}
#[cfg(test)]
test_atomic_counter!(
REGION_SCALED_COLOR_PLAN_BUILDS,
reset_region_scaled_color_plan_builds_for_test,
region_scaled_color_plan_builds_for_test
);
#[cfg(test)]
static REGION_SCALED_COLOR_PLAN_TEST_LOCK: Mutex<()> = Mutex::new(());
#[cfg(test)]
pub(crate) fn region_scaled_color_plan_test_lock_for_test() -> std::sync::MutexGuard<'static, ()> {
REGION_SCALED_COLOR_PLAN_TEST_LOCK
.lock()
.expect("region scaled color plan test lock")
}
#[cfg(test)]
pub(crate) fn reset_region_scaled_color_plan_cache_for_test() {
if let Some(cache) = REGION_SCALED_COLOR_PLAN_CACHE.get() {
if let Ok(mut guard) = cache.lock() {
guard.clear();
}
}
}
enum PreparedRegionScaledDirectPlan {
Gray(crate::compute::PreparedDirectGrayscalePlan),
Color(crate::compute::PreparedDirectColorPlan),
}
pub(crate) fn decode_region_scaled_direct_to_surface(
input: &[u8],
fmt: PixelFormat,
roi: Rect,
scale: Downscale,
) -> Result<Option<Surface>, Error> {
let Some(prepared) = build_region_scaled_direct_plan(input, fmt, roi, scale)? else {
return Ok(None);
};
execute_region_scaled_direct_plan(prepared, fmt)
}
pub(crate) fn decode_region_scaled_direct_to_surface_with_session(
input: &[u8],
fmt: PixelFormat,
roi: Rect,
scale: Downscale,
session: &crate::MetalBackendSession,
) -> Result<Option<Surface>, Error> {
let Some(prepared) =
build_region_scaled_direct_plan_with_session(input, fmt, roi, scale, session)?
else {
return Ok(None);
};
execute_region_scaled_direct_plan_with_device(prepared, fmt, &session.device)
}
fn build_region_scaled_direct_plan(
input: &[u8],
fmt: PixelFormat,
roi: Rect,
scale: Downscale,
) -> Result<Option<PreparedRegionScaledDirectPlan>, Error> {
match fmt {
PixelFormat::Gray8 | PixelFormat::Gray16 => {
match build_region_scaled_direct_gray_plan(input, roi, scale) {
Ok(plan) => Ok(Some(PreparedRegionScaledDirectPlan::Gray(plan))),
Err(error) if is_direct_region_scaled_runtime_fallback_error(&error) => Ok(None),
Err(error) => Err(error),
}
}
PixelFormat::Rgb8 | PixelFormat::Rgba8 | PixelFormat::Rgb16 => {
Ok(Some(PreparedRegionScaledDirectPlan::Color(
build_region_scaled_direct_color_plan(input, roi, scale)?,
)))
}
_ => Ok(None),
}
}
fn build_region_scaled_direct_plan_with_session(
input: &[u8],
fmt: PixelFormat,
roi: Rect,
scale: Downscale,
session: &crate::MetalBackendSession,
) -> Result<Option<PreparedRegionScaledDirectPlan>, Error> {
match fmt {
PixelFormat::Gray8 | PixelFormat::Gray16 => {
match build_region_scaled_direct_gray_plan(input, roi, scale) {
Ok(plan) => Ok(Some(PreparedRegionScaledDirectPlan::Gray(plan))),
Err(error) if is_direct_region_scaled_runtime_fallback_error(&error) => Ok(None),
Err(error) => Err(error),
}
}
PixelFormat::Rgb8 | PixelFormat::Rgba8 | PixelFormat::Rgb16 => {
Ok(Some(PreparedRegionScaledDirectPlan::Color(
(*build_region_scaled_direct_color_plan_cached_with_session(
input, roi, scale, session,
)?)
.clone(),
)))
}
_ => Ok(None),
}
}
#[doc(hidden)]
pub(crate) fn benchmark_region_scaled_direct_plan_prepare(
input: &[u8],
fmt: PixelFormat,
roi: Rect,
scale: Downscale,
) -> Result<(), Error> {
if build_region_scaled_direct_plan(input, fmt, roi, scale)?.is_some() {
Ok(())
} else {
Err(Error::UnsupportedMetalRequest {
reason: "J2K MetalDirect ROI+scaled plan preparation is unsupported for this benchmark input",
})
}
}
fn execute_region_scaled_direct_plan(
plan: PreparedRegionScaledDirectPlan,
fmt: PixelFormat,
) -> Result<Option<Surface>, Error> {
match plan {
PreparedRegionScaledDirectPlan::Gray(plan) => {
match crate::compute::execute_prepared_direct_grayscale_plan(&plan, fmt) {
Ok(surface) => Ok(Some(surface)),
Err(error) if is_direct_region_scaled_runtime_fallback_error(&error) => Ok(None),
Err(error) => Err(error),
}
}
PreparedRegionScaledDirectPlan::Color(plan) => {
match crate::compute::execute_hybrid_cpu_tier1_direct_color_plan(&plan, fmt) {
Ok(surface) => Ok(Some(surface)),
Err(error) if is_direct_region_scaled_runtime_fallback_error(&error) => {
Err(Error::UnsupportedMetalRequest {
reason: RGB_REGION_SCALED_METAL_DIRECT_UNSUPPORTED,
})
}
Err(error) => Err(error),
}
}
}
}
fn execute_region_scaled_direct_plan_with_device(
plan: PreparedRegionScaledDirectPlan,
fmt: PixelFormat,
device: &Device,
) -> Result<Option<Surface>, Error> {
match plan {
PreparedRegionScaledDirectPlan::Gray(plan) => {
match crate::compute::execute_prepared_direct_grayscale_plan_with_device(
&plan, fmt, device,
) {
Ok(surface) => Ok(Some(surface)),
Err(error) if is_direct_region_scaled_runtime_fallback_error(&error) => Ok(None),
Err(error) => Err(error),
}
}
PreparedRegionScaledDirectPlan::Color(plan) => {
match crate::compute::execute_hybrid_cpu_tier1_direct_color_plan_with_device(
&plan, fmt, device,
) {
Ok(surface) => Ok(Some(surface)),
Err(error) if is_direct_region_scaled_runtime_fallback_error(&error) => {
Err(Error::UnsupportedMetalRequest {
reason: RGB_REGION_SCALED_METAL_DIRECT_UNSUPPORTED,
})
}
Err(error) => Err(error),
}
}
}
}
pub(crate) fn decode_region_scaled_grayscale_batch_direct_to_device(
requests: &[(Arc<[u8]>, Rect, Downscale)],
fmt: PixelFormat,
) -> Result<Vec<Surface>, Error> {
if requests.is_empty() {
return Ok(Vec::new());
}
if !matches!(fmt, PixelFormat::Gray8 | PixelFormat::Gray16) {
return Err(Error::MetalKernel {
message: format!(
"J2K MetalDirect region-scaled grayscale batch does not support {fmt:?}"
),
});
}
let mut plans = Vec::with_capacity(requests.len());
for (input, roi, scale) in requests {
let plan = build_region_scaled_direct_gray_plan(input.as_ref(), *roi, *scale)?;
plans.push(Arc::new(plan));
}
crate::compute::execute_prepared_direct_grayscale_plan_batch(&plans, fmt)
}
pub(crate) fn decode_region_scaled_color_batch_direct_to_device(
requests: &[(Arc<[u8]>, Rect, Downscale)],
fmt: PixelFormat,
) -> Result<Vec<Surface>, Error> {
if requests.is_empty() {
return Ok(Vec::new());
}
if !matches!(
fmt,
PixelFormat::Rgb8 | PixelFormat::Rgba8 | PixelFormat::Rgb16
) {
return Err(Error::MetalKernel {
message: format!("J2K MetalDirect region-scaled color batch does not support {fmt:?}"),
});
}
if let Some((input, roi, scale)) = repeated_region_scaled_request(requests) {
let plan = build_region_scaled_direct_color_plan_cached(input.as_ref(), roi, scale)?;
let plans = vec![plan; requests.len()];
return crate::compute::execute_hybrid_cpu_tier1_direct_color_plan_batch(&plans, fmt);
}
let plans = requests
.par_iter()
.map(|(input, roi, scale)| {
build_region_scaled_direct_color_plan_cached(input.as_ref(), *roi, *scale)
})
.collect::<Result<Vec<_>, _>>()?;
crate::compute::execute_hybrid_cpu_tier1_direct_color_plan_batch(&plans, fmt)
}
pub(crate) fn decode_repeated_region_scaled_color_batch_direct_to_device(
input: &[u8],
roi: Rect,
scale: Downscale,
fmt: PixelFormat,
count: usize,
) -> Result<Vec<Surface>, Error> {
if count == 0 {
return Err(Error::MetalKernel {
message: "J2K MetalDirect repeated region-scaled color batch requires count > 0"
.to_string(),
});
}
if !matches!(
fmt,
PixelFormat::Rgb8 | PixelFormat::Rgba8 | PixelFormat::Rgb16
) {
return Err(Error::MetalKernel {
message: format!("J2K MetalDirect region-scaled color batch does not support {fmt:?}"),
});
}
let plan = build_region_scaled_direct_color_plan_cached(input, roi, scale)?;
let plans = vec![plan; count];
crate::compute::execute_hybrid_cpu_tier1_direct_color_plan_batch(&plans, fmt)
}
fn repeated_region_scaled_request(
requests: &[(Arc<[u8]>, Rect, Downscale)],
) -> Option<(&Arc<[u8]>, Rect, Downscale)> {
let (first_input, first_roi, first_scale) = requests.first()?;
requests
.iter()
.all(|(input, roi, scale)| {
*roi == *first_roi
&& *scale == *first_scale
&& (Arc::ptr_eq(input, first_input) || input.as_ref() == first_input.as_ref())
})
.then_some((first_input, *first_roi, *first_scale))
}
fn build_region_scaled_direct_gray_plan(
input: &[u8],
roi: Rect,
scale: Downscale,
) -> Result<crate::compute::PreparedDirectGrayscalePlan, Error> {
let image = build_region_scaled_native_image(input, scale)?;
let mut context = NativeDecoderContext::default();
let output_region = roi.scaled_covering(scale);
let plan = match image.build_direct_grayscale_plan_region_with_context(
&mut context,
(
output_region.x,
output_region.y,
output_region.w,
output_region.h,
),
) {
Ok(plan) => plan,
Err(error) if direct::is_unsupported_direct_plan_error(&error.to_string()) => {
return Err(Error::MetalKernel {
message: format!(
"explicit J2K MetalDirect region-scaled batch currently supports grayscale direct plans only: {error}"
),
});
}
Err(error) => {
return Err(Error::Decode(J2kError::Backend(format!(
"failed to build J2K MetalDirect region-scaled grayscale plan: {error}"
))));
}
};
let mut prepared = crate::compute::prepare_direct_grayscale_plan(&plan)?;
crate::compute::crop_prepared_direct_grayscale_plan_to_output_region(
&mut prepared,
output_region,
)?;
Ok(prepared)
}
fn build_region_scaled_direct_color_plan(
input: &[u8],
roi: Rect,
scale: Downscale,
) -> Result<crate::compute::PreparedDirectColorPlan, Error> {
#[cfg(test)]
REGION_SCALED_COLOR_PLAN_BUILDS.fetch_add(1, Ordering::Relaxed);
let profile_stages = crate::compute::metal_profile_stages_enabled();
let total_started = profile_stages.then(Instant::now);
let native_image_started = profile_stages.then(Instant::now);
let image = build_region_scaled_native_image(input, scale)?;
let native_image_us = native_image_started.map(elapsed_us).unwrap_or_default();
let direct_plan_started = profile_stages.then(Instant::now);
let mut context = NativeDecoderContext::default();
let output_region = roi.scaled_covering(scale);
let plan = match image.build_direct_color_plan_region_with_context(
&mut context,
(
output_region.x,
output_region.y,
output_region.w,
output_region.h,
),
) {
Ok(plan) => plan,
Err(error) if direct::is_unsupported_direct_plan_error(&error.to_string()) => {
return Err(Error::UnsupportedMetalRequest {
reason: RGB_REGION_SCALED_METAL_DIRECT_UNSUPPORTED,
});
}
Err(error) => {
return Err(Error::Decode(J2kError::Backend(format!(
"failed to build J2K MetalDirect region-scaled color plan: {error}"
))));
}
};
let direct_plan_us = direct_plan_started.map(elapsed_us).unwrap_or_default();
let prepare_started = profile_stages.then(Instant::now);
let mut prepared = crate::compute::prepare_direct_color_plan_for_cpu_upload(&plan)?;
let prepare_us = prepare_started.map(elapsed_us).unwrap_or_default();
let crop_started = profile_stages.then(Instant::now);
crate::compute::crop_prepared_direct_color_plan_to_output_region(&mut prepared, output_region)?;
let crop_us = crop_started.map(elapsed_us).unwrap_or_default();
if let Some(started) = total_started {
emit_region_scaled_color_plan_build_timings(
native_image_us,
direct_plan_us,
prepare_us,
crop_us,
elapsed_us(started),
);
}
Ok(prepared)
}
fn build_region_scaled_direct_color_plan_cached(
input: &[u8],
roi: Rect,
scale: Downscale,
) -> Result<Arc<crate::compute::PreparedDirectColorPlan>, Error> {
let cache_key = region_scaled_color_plan_cache_key(input, roi, scale);
if let Some(plan) = cached_region_scaled_direct_color_plan(cache_key) {
return Ok(plan);
}
let plan = Arc::new(build_region_scaled_direct_color_plan(input, roi, scale)?);
store_region_scaled_direct_color_plan(cache_key, plan.clone());
Ok(plan)
}
fn build_region_scaled_direct_color_plan_cached_with_session(
input: &[u8],
roi: Rect,
scale: Downscale,
session: &crate::MetalBackendSession,
) -> Result<Arc<crate::compute::PreparedDirectColorPlan>, Error> {
let cache_key = region_scaled_color_plan_cache_key(input, roi, scale);
if let Some(plan) = cached_region_scaled_direct_color_plan_with_session(session, cache_key) {
return Ok(plan);
}
let plan = Arc::new(build_region_scaled_direct_color_plan(input, roi, scale)?);
store_region_scaled_direct_color_plan_with_session(session, cache_key, plan.clone());
Ok(plan)
}
fn cached_region_scaled_direct_color_plan_with_session(
session: &crate::MetalBackendSession,
key: u64,
) -> Option<Arc<crate::compute::PreparedDirectColorPlan>> {
let guard = session.region_scaled_color_plan_cache.lock().ok()?;
guard.get(&key).cloned()
}
fn store_region_scaled_direct_color_plan_with_session(
session: &crate::MetalBackendSession,
key: u64,
plan: Arc<crate::compute::PreparedDirectColorPlan>,
) {
if let Ok(mut guard) = session.region_scaled_color_plan_cache.lock() {
evict_one_region_scaled_color_plan_if_needed(&mut guard);
guard.insert(key, plan);
}
}
fn region_scaled_color_plan_cache_key(input: &[u8], roi: Rect, scale: Downscale) -> u64 {
let mut hasher = DefaultHasher::new();
input.len().hash(&mut hasher);
input.hash(&mut hasher);
roi.hash(&mut hasher);
scale.hash(&mut hasher);
hasher.finish()
}
fn cached_region_scaled_direct_color_plan(
key: u64,
) -> Option<Arc<crate::compute::PreparedDirectColorPlan>> {
let cache = REGION_SCALED_COLOR_PLAN_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
let guard = cache.lock().ok()?;
guard.get(&key).cloned()
}
fn store_region_scaled_direct_color_plan(
key: u64,
plan: Arc<crate::compute::PreparedDirectColorPlan>,
) {
let cache = REGION_SCALED_COLOR_PLAN_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
if let Ok(mut guard) = cache.lock() {
evict_one_region_scaled_color_plan_if_needed(&mut guard);
guard.insert(key, plan);
}
}
fn evict_one_region_scaled_color_plan_if_needed<T>(cache: &mut HashMap<u64, T>) {
if cache.len() < REGION_SCALED_COLOR_PLAN_CACHE_CAP {
return;
}
if let Some(key) = cache.keys().next().copied() {
cache.remove(&key);
}
}
fn elapsed_us(started: Instant) -> u128 {
started.elapsed().as_micros()
}
fn emit_region_scaled_color_plan_build_timings(
native_image_us: u128,
direct_plan_us: u128,
prepare_us: u128,
crop_us: u128,
total_us: u128,
) {
if !crate::compute::metal_profile_stages_enabled() {
return;
}
let label = decode_profile_label();
for (stage, elapsed_us) in [
("native_image", native_image_us),
("direct_color_plan", direct_plan_us),
("prepare_cpu_upload", prepare_us),
("crop_prepared_plan", crop_us),
("plan_total", total_us),
] {
let processor = plan_stage_processor(stage);
let metric = plan_stage_metric(stage);
let metric_kind = plan_stage_metric_kind(stage);
let aggregation = plan_stage_aggregation(stage);
j2k_profile::emit_profile_row_now(
"j2k",
"decode",
"metal_cpu_hybrid_plan",
&[
("pipeline", "decode_hybrid".to_string()),
("label", label.clone()),
("stage", stage.to_string()),
("processor", processor.to_string()),
("metric", metric.to_string()),
("metric_kind", metric_kind.to_string()),
("aggregation", aggregation.to_string()),
("fmt", "Rgb".to_string()),
("batch_count", "1".to_string()),
("elapsed_us", elapsed_us.to_string()),
],
);
}
}
fn decode_profile_label() -> String {
std::env::var(METAL_PROFILE_DECODE_LABEL_ENV)
.ok()
.filter(|label| !label.is_empty())
.map_or_else(
|| "unlabeled".to_string(),
|label| sanitize_profile_label(&label),
)
}
fn sanitize_profile_label(label: &str) -> String {
label
.chars()
.map(|ch| {
if ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-' | '.') {
ch
} else {
'_'
}
})
.collect()
}
fn plan_stage_processor(stage: &str) -> &'static str {
match stage {
"native_image" | "direct_color_plan" | "prepare_cpu_upload" | "crop_prepared_plan" => "cpu",
_ => "hybrid",
}
}
fn plan_stage_metric(stage: &str) -> &'static str {
match stage {
"native_image" => "native_image_us",
"direct_color_plan" => "direct_color_plan_us",
"prepare_cpu_upload" => "prepare_cpu_upload_us",
"crop_prepared_plan" => "crop_prepared_plan_us",
"plan_total" => "plan_total_us",
_ => "wall_us",
}
}
fn plan_stage_metric_kind(_stage: &str) -> &'static str {
"wall_elapsed"
}
fn plan_stage_aggregation(stage: &str) -> &'static str {
match stage {
"plan_total" => "inclusive",
_ => "exclusive",
}
}
fn build_region_scaled_native_image(
input: &[u8],
scale: Downscale,
) -> Result<NativeImage<'_>, Error> {
let decoder = J2kDecoder::new(input)?;
let dims = decoder.inner.info().dimensions;
let target_dims = (
dims.0.div_ceil(scale.denominator()),
dims.1.div_ceil(scale.denominator()),
);
let settings = NativeDecodeSettings {
target_resolution: Some(target_dims),
..NativeDecodeSettings::default()
};
let image =
NativeImage::new(input, &settings).map_err(|error| J2kError::Backend(error.to_string()))?;
Ok(image)
}
fn is_direct_region_scaled_runtime_fallback_error(error: &Error) -> bool {
crate::is_direct_runtime_fallback_error(error)
}
#[cfg(test)]
mod tests {
use super::*;
fn encoded_rgb8_tile_for_region_scaled_plan_cache(seed: u8) -> Arc<[u8]> {
let mut pixels = j2k_test_support::gradient_u8(64, 64, 3);
for pixel in pixels.chunks_exact_mut(3) {
pixel[0] = pixel[0].wrapping_add(seed);
pixel[1] = pixel[1].wrapping_add(seed.wrapping_mul(3));
pixel[2] = pixel[2].wrapping_add(seed.wrapping_mul(5));
}
let options = j2k_native::EncodeOptions {
reversible: true,
num_decomposition_levels: 2,
..j2k_native::EncodeOptions::default()
};
Arc::<[u8]>::from(
j2k_native::encode(&pixels, 64, 64, 3, 8, false, &options).expect("encode rgb8"),
)
}
fn region_scaled_plan_cache_roi() -> Rect {
Rect {
x: 8,
y: 8,
w: 32,
h: 32,
}
}
#[test]
fn known_repeated_region_scaled_color_batch_builds_one_plan() {
let _guard = region_scaled_color_plan_test_lock_for_test();
reset_region_scaled_color_plan_builds_for_test();
let input = Arc::<[u8]>::from([1_u8, 2, 3, 4]);
let roi = Rect {
x: 0,
y: 0,
w: 64,
h: 64,
};
let result = decode_repeated_region_scaled_color_batch_direct_to_device(
input.as_ref(),
roi,
Downscale::Half,
PixelFormat::Rgb8,
4,
);
assert!(result.is_err());
assert_eq!(region_scaled_color_plan_builds_for_test(), 1);
}
#[test]
fn known_repeated_region_scaled_color_batch_rejects_zero_count() {
let _guard = region_scaled_color_plan_test_lock_for_test();
reset_region_scaled_color_plan_builds_for_test();
let result = decode_repeated_region_scaled_color_batch_direct_to_device(
&[1_u8, 2, 3, 4],
Rect {
x: 0,
y: 0,
w: 64,
h: 64,
},
Downscale::Half,
PixelFormat::Rgb8,
0,
);
assert!(matches!(result, Err(Error::MetalKernel { .. })));
assert_eq!(region_scaled_color_plan_builds_for_test(), 0);
}
#[cfg(target_os = "macos")]
#[test]
fn known_repeated_region_scaled_color_batch_reuses_cached_plan_across_calls() {
if Device::system_default().is_none() {
eprintln!("skipping repeated color plan cache test: no Metal device");
return;
}
let _guard = region_scaled_color_plan_test_lock_for_test();
let input = encoded_rgb8_tile_for_region_scaled_plan_cache(17);
let roi = region_scaled_plan_cache_roi();
reset_region_scaled_color_plan_cache_for_test();
reset_region_scaled_color_plan_builds_for_test();
for _ in 0..2 {
let surfaces = decode_repeated_region_scaled_color_batch_direct_to_device(
input.as_ref(),
roi,
Downscale::Quarter,
PixelFormat::Rgb8,
4,
)
.expect("repeated RGB region-scaled batch");
assert_eq!(surfaces.len(), 4);
}
assert_eq!(
region_scaled_color_plan_builds_for_test(),
1,
"same RGB ROI+scaled batch should reuse the prepared direct color plan across calls"
);
}
#[cfg(target_os = "macos")]
#[test]
fn known_distinct_region_scaled_color_batch_reuses_cached_plans_across_calls() {
if Device::system_default().is_none() {
eprintln!("skipping distinct color plan cache test: no Metal device");
return;
}
let _guard = region_scaled_color_plan_test_lock_for_test();
let first = encoded_rgb8_tile_for_region_scaled_plan_cache(29);
let second = encoded_rgb8_tile_for_region_scaled_plan_cache(43);
let roi = region_scaled_plan_cache_roi();
let requests = vec![
(first, roi, Downscale::Quarter),
(second, roi, Downscale::Quarter),
];
reset_region_scaled_color_plan_cache_for_test();
reset_region_scaled_color_plan_builds_for_test();
for _ in 0..2 {
let surfaces =
decode_region_scaled_color_batch_direct_to_device(&requests, PixelFormat::Rgb8)
.expect("distinct RGB region-scaled batch");
assert_eq!(surfaces.len(), requests.len());
}
assert_eq!(
region_scaled_color_plan_builds_for_test(),
2,
"same distinct RGB ROI+scaled inputs should reuse prepared direct color plans across calls"
);
}
}