//! CPU-kernel benchmarks for the {{ display_name }} effect (multi-pass).
//!
//! Drives the same `{{ pass1_kernel_name }}` and `{{ pass2_kernel_name }}`
//! kernels that Premiere / AE invokes at render time, but entirely offline —
//! no After Effects / Premiere host, no Layer plumbing.
//!
//! Three bench targets are exposed:
//!
//!   1. `{{ pass1_kernel_name }}` — standalone edge detection pass.
//!   2. `{{ pass2_kernel_name }}` — standalone composite pass.
//!   3. `full_pipeline`          — two-pass: edge → composite, mirrors
//!      `Plugin::render`.
//!
//! Customization guide
//! -------------------
//!
//! - **Add resolutions / formats**: extend the slices in each bench fn.
//! - **Change parameter defaults**: edit `default_edge_params()` /
//!   `default_composite_params()`.
//! - **Adjust sample size / measurement time**: `.sample_size(N)` /
//!   `.measurement_time(Duration::from_secs(T))`.

use std::time::Duration;

use prgpu::bench::*;

use {{ crate_name }}::kernel::{
	{{ pass1_kernel_params_name }},
	{{ pass2_kernel_params_name }},
	{{ pass1_kernel_name | upper }}_CPU_DISPATCH_TILE,
	{{ pass2_kernel_name | upper }}_CPU_DISPATCH_TILE,
};

fn default_edge_params() -> {{ pass1_kernel_params_name }} {
	{{ pass1_kernel_params_name }} {
		edge_strength: 1.0,
		edge_threshold: 0.1,
		edge_width: 1.0,
		edge_src_width: 0,
		edge_src_height: 0,
		edge_src_pitch: 0,
	}
}

fn default_composite_params() -> {{ pass2_kernel_params_name }} {
	{{ pass2_kernel_params_name }} {
		opacity: 1.0,
		tint_r: 0.0,
		tint_g: 0.0,
		tint_b: 0.0,
		tint_a: 1.0,
		edge_width: 0,
		edge_height: 0,
		edge_pitch: 0,
	}
}

/// Bench 1 — standalone edge detection. Per-scene `customize` populates the
/// `edge_src_*` fields so the shader can clamp correctly against a buffer
/// whose dims match the dispatch.
fn bench_{{ pass1_kernel_name }}(c: &mut Criterion) {
	KernelBenchmark::new("{{ pass1_kernel_name }}", {{ pass1_kernel_name | upper }}_CPU_DISPATCH_TILE, default_edge_params())
		.resolutions(&[Resolution::HD720, Resolution::HD1080])
		.formats(&[PixelFormat::Bgra8, PixelFormat::Bgra32f])
		.customize(|p, res, _fmt| {
			let (w, h) = res.dims();
			p.edge_src_width = w;
			p.edge_src_height = h;
			p.edge_src_pitch = w;
		})
		.sample_size(20)
		.measurement_time(Duration::from_secs(5))
		.run(c);
}

/// Bench 2 — standalone composite. The aux buffer is mocked out with the same
/// dims / pitch as the main scene so we measure the pure read+blend cost.
fn bench_{{ pass2_kernel_name }}(c: &mut Criterion) {
	KernelBenchmark::new("{{ pass2_kernel_name }}", {{ pass2_kernel_name | upper }}_CPU_DISPATCH_TILE, default_composite_params())
		.resolutions(&[Resolution::HD720, Resolution::HD1080])
		.formats(&[PixelFormat::Bgra8, PixelFormat::Bgra32f])
		.customize(|p, res, _fmt| {
			let (w, h) = res.dims();
			p.edge_width = w;
			p.edge_height = h;
			p.edge_pitch = w;
		})
		.sample_size(20)
		.measurement_time(Duration::from_secs(5))
		.run(c);
}

/// Bench 3 — full two-pass pipeline (edge → composite). Mirrors what
/// `Plugin::render` does in production: pass 1 writes the edge mask into an
/// aux buffer, pass 2 consumes it together with the original input.
fn bench_full_pipeline(c: &mut Criterion) {
	let base_edge = default_edge_params();
	let base_comp = default_composite_params();

	let mut group = c.benchmark_group("full_pipeline");
	group.sample_size(15);
	group.measurement_time(Duration::from_secs(6));
	group.warm_up_time(Duration::from_secs(1));

	for &fmt in &[PixelFormat::Bgra8, PixelFormat::Bgra32f] {
		for &res in &[Resolution::HD720, Resolution::HD1080] {
			let (w, h) = res.dims();
			group.throughput(Throughput::Elements((w as u64) * (h as u64)));

			let mut edge_p = base_edge;
			edge_p.edge_src_width = w;
			edge_p.edge_src_height = h;
			edge_p.edge_src_pitch = w;

			let mut comp_p = base_comp;
			comp_p.edge_width = w;
			comp_p.edge_height = h;
			comp_p.edge_pitch = w;

			let id = BenchmarkId::new(fmt.label(), res.label());
			group.bench_function(id, move |b| {
				let mut scene = Scene::new(res, fmt);
				let aux_idx = scene.alloc_aux(w, h);

				b.iter(|| {
					// Pass 1: edge detection (input → aux).
					let (aw, ah) = scene.aux_dims(aux_idx);
					let in_ptr = scene.input_ptr();
					let aux_ptr = scene.aux_ptr(aux_idx);
					let edge_cfg = prgpu::types::Configuration::cpu(
						in_ptr,
						aux_ptr,
						scene.pitch_px,
						aw as i32,
						aw,
						ah,
						fmt.bpp(),
						fmt.layout_id(),
					);
					scene.dispatch_with(&edge_cfg, {{ pass1_kernel_name | upper }}_CPU_DISPATCH_TILE, &edge_p);

					// Pass 2: composite (aux → output, with original input as
					// incoming). outgoing = aux (edge mask), incoming = input,
					// dest = output.
					let aux_ptr = scene.aux_ptr(aux_idx);
					let in_ptr = scene.input_ptr();
					let out_ptr = scene.output_ptr();
					let mut comp_cfg = prgpu::types::Configuration::cpu(
						aux_ptr,
						out_ptr,
						aw as i32,
						scene.pitch_px,
						w,
						h,
						fmt.bpp(),
						fmt.layout_id(),
					);
					comp_cfg.outgoing_width = aw;
					comp_cfg.outgoing_height = ah;
					comp_cfg.incoming_data = Some(in_ptr);
					comp_cfg.incoming_pitch_px = scene.pitch_px;
					scene.dispatch_with(&comp_cfg, {{ pass2_kernel_name | upper }}_CPU_DISPATCH_TILE, &comp_p);

					std::hint::black_box(scene.output_ptr());
				});
			});
		}
	}
	group.finish();
}

criterion_group!(
	name = benches;
	config = Criterion::default();
	targets = bench_{{ pass1_kernel_name }}, bench_{{ pass2_kernel_name }}, bench_full_pipeline
);
criterion_main!(benches);
