#![allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WorkItemKind {
Compute,
Scale,
ColorConvert,
MotionEstimate,
Transfer,
}
impl WorkItemKind {
#[must_use]
pub fn is_compute(&self) -> bool {
!matches!(self, Self::Transfer)
}
#[must_use]
pub fn label(&self) -> &'static str {
match self {
Self::Compute => "compute",
Self::Scale => "scale",
Self::ColorConvert => "color_convert",
Self::MotionEstimate => "motion_estimate",
Self::Transfer => "transfer",
}
}
}
#[derive(Debug, Clone)]
pub struct WorkItem {
pub id: u64,
pub kind: WorkItemKind,
pub width: u32,
pub height: u32,
pub priority: u8,
}
impl WorkItem {
#[must_use]
pub fn new(id: u64, kind: WorkItemKind, width: u32, height: u32) -> Self {
Self { id, kind, width, height, priority: 0 }
}
#[must_use]
pub fn with_priority(id: u64, kind: WorkItemKind, width: u32, height: u32, priority: u8) -> Self {
Self { id, kind, width, height, priority }
}
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn estimated_cycles(&self) -> u64 {
let pixels = u64::from(self.width) * u64::from(self.height);
let cost_per_pixel: u64 = match self.kind {
WorkItemKind::Compute => 8,
WorkItemKind::Scale => 12,
WorkItemKind::ColorConvert => 6,
WorkItemKind::MotionEstimate => 32,
WorkItemKind::Transfer => 2,
};
pixels * cost_per_pixel
}
}
#[derive(Debug, Default)]
pub struct WorkItemBatch {
items: Vec<WorkItem>,
}
impl WorkItemBatch {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, item: WorkItem) {
self.items.push(item);
}
#[must_use]
pub fn total_cycles(&self) -> u64 {
self.items.iter().map(WorkItem::estimated_cycles).sum()
}
#[must_use]
pub fn len(&self) -> usize {
self.items.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &WorkItem> {
self.items.iter()
}
}
#[derive(Debug, Default)]
pub struct WorkItemScheduler {
queue: Vec<WorkItem>,
}
impl WorkItemScheduler {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn schedule(&mut self, batch: WorkItemBatch) -> Vec<WorkItem> {
let mut items: Vec<WorkItem> = batch.items;
items.sort_by(|a, b| {
b.priority
.cmp(&a.priority)
.then_with(|| a.estimated_cycles().cmp(&b.estimated_cycles()))
});
self.queue.extend(items.iter().cloned());
items
}
#[must_use]
pub fn total_scheduled(&self) -> usize {
self.queue.len()
}
pub fn reset(&mut self) {
self.queue.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_item(id: u64, kind: WorkItemKind, w: u32, h: u32) -> WorkItem {
WorkItem::new(id, kind, w, h)
}
#[test]
fn test_kind_is_compute_true() {
assert!(WorkItemKind::Compute.is_compute());
}
#[test]
fn test_kind_is_compute_scale() {
assert!(WorkItemKind::Scale.is_compute());
}
#[test]
fn test_kind_is_compute_transfer_false() {
assert!(!WorkItemKind::Transfer.is_compute());
}
#[test]
fn test_kind_label() {
assert_eq!(WorkItemKind::ColorConvert.label(), "color_convert");
assert_eq!(WorkItemKind::MotionEstimate.label(), "motion_estimate");
assert_eq!(WorkItemKind::Transfer.label(), "transfer");
}
#[test]
fn test_work_item_estimated_cycles_compute() {
let item = make_item(1, WorkItemKind::Compute, 1920, 1080);
assert_eq!(item.estimated_cycles(), 1920 * 1080 * 8);
}
#[test]
fn test_work_item_estimated_cycles_motion() {
let item = make_item(2, WorkItemKind::MotionEstimate, 10, 10);
assert_eq!(item.estimated_cycles(), 10 * 10 * 32);
}
#[test]
fn test_work_item_estimated_cycles_transfer_cheapest() {
let transfer = make_item(3, WorkItemKind::Transfer, 100, 100);
let compute = make_item(4, WorkItemKind::Compute, 100, 100);
assert!(transfer.estimated_cycles() < compute.estimated_cycles());
}
#[test]
fn test_work_item_with_priority() {
let item = WorkItem::with_priority(5, WorkItemKind::Scale, 640, 480, 10);
assert_eq!(item.priority, 10);
}
#[test]
fn test_batch_add_and_len() {
let mut batch = WorkItemBatch::new();
batch.add(make_item(1, WorkItemKind::Compute, 100, 100));
batch.add(make_item(2, WorkItemKind::Scale, 200, 200));
assert_eq!(batch.len(), 2);
assert!(!batch.is_empty());
}
#[test]
fn test_batch_empty() {
let batch = WorkItemBatch::new();
assert!(batch.is_empty());
assert_eq!(batch.total_cycles(), 0);
}
#[test]
fn test_batch_total_cycles() {
let mut batch = WorkItemBatch::new();
batch.add(make_item(1, WorkItemKind::Transfer, 10, 10)); batch.add(make_item(2, WorkItemKind::Compute, 10, 10)); assert_eq!(batch.total_cycles(), 1000);
}
#[test]
fn test_scheduler_orders_by_priority() {
let mut sched = WorkItemScheduler::new();
let mut batch = WorkItemBatch::new();
batch.add(WorkItem::with_priority(1, WorkItemKind::Compute, 10, 10, 1));
batch.add(WorkItem::with_priority(2, WorkItemKind::Scale, 10, 10, 5));
let ordered = sched.schedule(batch);
assert_eq!(ordered[0].id, 2); assert_eq!(ordered[1].id, 1);
}
#[test]
fn test_scheduler_total_scheduled() {
let mut sched = WorkItemScheduler::new();
let mut batch = WorkItemBatch::new();
batch.add(make_item(1, WorkItemKind::Compute, 10, 10));
batch.add(make_item(2, WorkItemKind::Scale, 10, 10));
sched.schedule(batch);
assert_eq!(sched.total_scheduled(), 2);
}
#[test]
fn test_scheduler_reset() {
let mut sched = WorkItemScheduler::new();
let mut batch = WorkItemBatch::new();
batch.add(make_item(1, WorkItemKind::Transfer, 8, 8));
sched.schedule(batch);
sched.reset();
assert_eq!(sched.total_scheduled(), 0);
}
#[test]
fn test_batch_iter() {
let mut batch = WorkItemBatch::new();
batch.add(make_item(42, WorkItemKind::ColorConvert, 1, 1));
let ids: Vec<u64> = batch.iter().map(|i| i.id).collect();
assert_eq!(ids, vec![42]);
}
}