use jxr_core::device_plan::{OUTPUT_PLANE, SURFACE_HEIGHT, SURFACE_WIDTH};
use std::sync::Arc;
use cudarc::driver::{CudaEvent, CudaSlice, CudaStream, LaunchConfig, PushKernelArg};
use crate::{
CudaDecodePlan, CudaError,
abi::{JxrOverlapWorkAbi, JxrPlaneAbi, JxrSamplePlaneAbi, JxrSurfacePlaneAbi},
output_plan::{OutputDispatchPlan, StorePipeline, build_output_dispatch},
overlap_plan::{OverlapSchedule, first_overlap_schedule, second_overlap_schedule},
plan::{CudaReconstructionInput, reconstruction_arena},
runtime::{BATCH_SCRATCH_BUDGET, CudaRuntime},
upload_cache::DeviceArena,
};
pub(crate) struct EncodedCudaSubmission {
pub(crate) completion: CudaEvent,
pub(crate) stream: Arc<CudaStream>,
pub(crate) output: Option<CudaSlice<u8>>,
pub(crate) status: CudaSlice<u32>,
pub(crate) scratch: Vec<CudaSlice<i32>>,
pub(crate) _overlap_work: Vec<CudaSlice<JxrOverlapWorkAbi>>,
pub(crate) _sample_planes: CudaSlice<JxrSamplePlaneAbi>,
pub(crate) _surface_planes: CudaSlice<JxrSurfacePlaneAbi>,
pub(crate) _uploads: Vec<Arc<DeviceArena>>,
pub(crate) runtime: Arc<CudaRuntime>,
pub(crate) layout: jxr_core::SurfaceLayout,
}
pub(crate) fn encode_owned(
runtime: &Arc<CudaRuntime>,
plan: &CudaDecodePlan,
stream_index: usize,
) -> Result<EncodedCudaSubmission, CudaError> {
let stream = runtime.stream(stream_index);
let mut output = stream.alloc_zeros(plan.output().byte_len)?;
let mut encoded = encode_impl(runtime, plan, &mut output, 0, stream_index)?;
encoded.output = Some(output);
Ok(encoded)
}
pub(crate) fn encode_into(
runtime: &Arc<CudaRuntime>,
plan: &CudaDecodePlan,
output: &mut CudaSlice<u8>,
output_base: usize,
stream_index: usize,
) -> Result<EncodedCudaSubmission, CudaError> {
let end =
output_base
.checked_add(plan.output().byte_len)
.ok_or(CudaError::InvalidDestination {
reason: "CUDA output range overflows usize",
})?;
if end > output.len() {
return Err(CudaError::InvalidDestination {
reason: "CUDA output range exceeds the destination",
});
}
encode_impl(runtime, plan, output, output_base, stream_index)
}
fn encode_impl(
runtime: &Arc<CudaRuntime>,
plan: &CudaDecodePlan,
output: &mut CudaSlice<u8>,
output_base: usize,
stream_index: usize,
) -> Result<EncodedCudaSubmission, CudaError> {
if plan.scratch_bytes()? > BATCH_SCRATCH_BUDGET {
return Err(CudaError::ResourceLimit {
reason: "one image exceeds the CUDA scratch budget",
requested: plan.scratch_bytes()?,
maximum: BATCH_SCRATCH_BUDGET,
});
}
let output_base = u32::try_from(output_base).map_err(|_| CudaError::InvalidDestination {
reason: "CUDA output base exceeds the reconstruction ABI",
})?;
let _submission = runtime
.submission_lock
.lock()
.map_err(|_| CudaError::StatePoisoned {
state: "CUDA submission encoder",
})?;
let input = plan.reconstruction()?;
let stream = runtime.stream(stream_index).clone();
let mut uploads = Vec::with_capacity(input.arenas.len());
for arena in input.arenas.iter() {
uploads.push(runtime.upload_cache.get_or_upload(
stream_index % runtime.streams.len(),
&stream,
arena.source.clone(),
)?);
}
let mut low = runtime.buffer_pool.take(&stream, input.low_len)?;
let mut samples = runtime.buffer_pool.take(&stream, input.sample_len)?;
let mut status = stream.alloc_zeros::<u32>(1)?;
let mut overlap_work = Vec::new();
launch_first_transforms(runtime, &stream, input, &uploads, &mut low, &mut status)?;
launch_first_overlaps(
runtime,
&stream,
input,
&mut low,
&mut status,
&mut overlap_work,
)?;
launch_second_transforms(
runtime,
&stream,
input,
&uploads,
&low,
&mut samples,
&mut status,
)?;
launch_second_overlaps(
runtime,
&stream,
input,
&mut samples,
&mut status,
&mut overlap_work,
)?;
let output_plan = build_output_dispatch(plan)?;
let sample_planes = stream.clone_htod(&output_plan.samples)?;
let surface_planes = stream.clone_htod(&output_plan.surfaces)?;
launch_output(
runtime,
&stream,
&samples,
&sample_planes,
&surface_planes,
output,
&mut status,
&output_plan,
output_base,
)?;
let completion = stream.record_event(None)?;
Ok(EncodedCudaSubmission {
completion,
stream,
output: None,
status,
scratch: vec![low, samples],
_overlap_work: overlap_work,
_sample_planes: sample_planes,
_surface_planes: surface_planes,
_uploads: uploads,
runtime: runtime.clone(),
layout: plan.output().clone(),
})
}
fn launch_first_transforms(
runtime: &CudaRuntime,
stream: &Arc<CudaStream>,
input: &CudaReconstructionInput,
uploads: &[Arc<DeviceArena>],
low: &mut CudaSlice<i32>,
status: &mut CudaSlice<u32>,
) -> Result<(), CudaError> {
for &plane in input.planes.iter() {
let abi = JxrPlaneAbi::from_plan(plane)?;
let arena = arena(uploads, plane.arena_index)?;
let function = runtime.function("jxr_dequantize_first_transform")?;
let mut launch = stream.launch_builder(function);
launch
.arg(&arena.coefficients)
.arg(&arena.macroblocks)
.arg(&mut *low)
.arg(&mut *status)
.arg(&abi);
unsafe {
launch.launch(linear_config(abi.macroblock_count, 128)?)?;
}
}
Ok(())
}
fn launch_first_overlaps(
runtime: &CudaRuntime,
stream: &Arc<CudaStream>,
input: &CudaReconstructionInput,
low: &mut CudaSlice<i32>,
status: &mut CudaSlice<u32>,
retained: &mut Vec<CudaSlice<JxrOverlapWorkAbi>>,
) -> Result<(), CudaError> {
for &plane in input.planes.iter() {
let arena = reconstruction_arena(input, plane.arena_index)?;
if arena.overlap != jxr_core::OverlapMode::Two {
continue;
}
let schedule = first_overlap_schedule(
plane,
arena.hard_tiles,
&arena.tile_column_widths,
&arena.tile_row_heights,
)?;
launch_schedule(
runtime,
stream,
"jxr_first_overlap",
low,
status,
schedule,
retained,
)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn launch_second_transforms(
runtime: &CudaRuntime,
stream: &Arc<CudaStream>,
input: &CudaReconstructionInput,
uploads: &[Arc<DeviceArena>],
low: &CudaSlice<i32>,
samples: &mut CudaSlice<i32>,
status: &mut CudaSlice<u32>,
) -> Result<(), CudaError> {
for &plane in input.planes.iter() {
let abi = JxrPlaneAbi::from_plan(plane)?;
let arena = arena(uploads, plane.arena_index)?;
let function = runtime.function("jxr_highpass_second_transform")?;
let mut launch = stream.launch_builder(function);
launch
.arg(&arena.coefficients)
.arg(&arena.macroblocks)
.arg(low)
.arg(&mut *samples)
.arg(&mut *status)
.arg(&abi);
unsafe {
launch.launch(LaunchConfig {
grid_dim: (abi.macroblock_count, 1, 1),
block_dim: (16, 1, 1),
shared_mem_bytes: 256 * 4,
})?;
}
}
Ok(())
}
fn launch_second_overlaps(
runtime: &CudaRuntime,
stream: &Arc<CudaStream>,
input: &CudaReconstructionInput,
samples: &mut CudaSlice<i32>,
status: &mut CudaSlice<u32>,
retained: &mut Vec<CudaSlice<JxrOverlapWorkAbi>>,
) -> Result<(), CudaError> {
for &plane in input.planes.iter() {
let arena = reconstruction_arena(input, plane.arena_index)?;
if arena.overlap == jxr_core::OverlapMode::None {
continue;
}
let schedule = second_overlap_schedule(
plane,
arena.hard_tiles,
&arena.tile_column_widths,
&arena.tile_row_heights,
)?;
launch_schedule(
runtime,
stream,
"jxr_second_overlap",
samples,
status,
schedule,
retained,
)?;
}
Ok(())
}
fn arena(uploads: &[Arc<DeviceArena>], index: u32) -> Result<&DeviceArena, CudaError> {
uploads
.get(usize::try_from(index).map_err(|_| CudaError::InvalidPlan {
reason: "coefficient arena index does not fit usize",
})?)
.map(AsRef::as_ref)
.ok_or(CudaError::InvalidPlan {
reason: "coefficient arena index is out of range",
})
}
#[allow(clippy::too_many_arguments)]
fn launch_output(
runtime: &CudaRuntime,
stream: &Arc<CudaStream>,
samples: &CudaSlice<i32>,
sample_planes: &CudaSlice<JxrSamplePlaneAbi>,
surface_planes: &CudaSlice<JxrSurfacePlaneAbi>,
output: &mut CudaSlice<u8>,
status: &mut CudaSlice<u32>,
dispatch: &OutputDispatchPlan,
output_base: u32,
) -> Result<(), CudaError> {
let function = runtime.function(dispatch.pipeline.entrypoint())?;
let plane_count = if dispatch.planar {
dispatch.surfaces.len()
} else {
1
};
for plane_index in 0..plane_count {
let mut params = dispatch.params;
params[OUTPUT_PLANE] = u32::try_from(plane_index).map_err(|_| CudaError::InvalidPlan {
reason: "output plane index exceeds the CUDA ABI",
})?;
let surface = dispatch.surfaces[plane_index];
let width = if dispatch.pipeline == StorePipeline::Bits {
surface[SURFACE_WIDTH].div_ceil(8)
} else {
surface[SURFACE_WIDTH]
};
let mut launch = stream.launch_builder(function);
launch
.arg(samples)
.arg(sample_planes)
.arg(surface_planes)
.arg(&mut *output)
.arg(&mut *status)
.arg(¶ms)
.arg(&output_base);
unsafe {
launch.launch(LaunchConfig {
grid_dim: (width.div_ceil(16), surface[SURFACE_HEIGHT].div_ceil(16), 1),
block_dim: (16, 16, 1),
shared_mem_bytes: 0,
})?;
}
}
Ok(())
}
fn launch_schedule(
runtime: &CudaRuntime,
stream: &Arc<CudaStream>,
entrypoint: &'static str,
samples: &mut CudaSlice<i32>,
status: &mut CudaSlice<u32>,
schedule: OverlapSchedule,
retained: &mut Vec<CudaSlice<JxrOverlapWorkAbi>>,
) -> Result<(), CudaError> {
for work in [schedule.prefix, schedule.filters, schedule.suffix] {
if work.is_empty() {
continue;
}
let work_count = u32::try_from(work.len()).map_err(|_| CudaError::InvalidPlan {
reason: "overlap work count exceeds the CUDA ABI",
})?;
let work = stream.clone_htod(&work)?;
let function = runtime.function(entrypoint)?;
let mut launch = stream.launch_builder(function);
launch
.arg(&mut *samples)
.arg(&work)
.arg(&mut *status)
.arg(&work_count);
unsafe {
launch.launch(linear_config(work_count, 128)?)?;
}
retained.push(work);
}
Ok(())
}
fn linear_config(elements: u32, threads: u32) -> Result<LaunchConfig, CudaError> {
if elements == 0 || threads == 0 {
return Err(CudaError::InvalidPlan {
reason: "CUDA launch dimensions must be nonzero",
});
}
Ok(LaunchConfig {
grid_dim: (elements.div_ceil(threads), 1, 1),
block_dim: (threads, 1, 1),
shared_mem_bytes: 0,
})
}