Browse Source

Skeleton of new dispatch system implemented.

Kestrel 5 months ago
parent
commit
d44183eada
12 changed files with 708 additions and 394 deletions
  1. 1 0
      .vimrc
  2. 297 0
      src/bitmap.rs
  3. 52 8
      src/colour.rs
  4. 0 90
      src/data.rs
  5. 162 0
      src/dispatch.rs
  6. 72 45
      src/formats.rs
  7. 22 209
      src/lib.rs
  8. 10 0
      src/op.rs
  9. 83 0
      src/op/fill.rs
  10. 7 40
      src/ops.rs
  11. 1 1
      src/ops/impls.rs
  12. 1 1
      tests/common/mod.rs

+ 1 - 0
.vimrc

@@ -0,0 +1 @@
+set wildignore+=target

+ 297 - 0
src/bitmap.rs

@@ -0,0 +1,297 @@
+use crate::{formats::PixelFormatSpec, math};
+
+pub trait BitmapData {
+    fn data(&self) -> &[u8];
+    fn width(&self) -> usize;
+    fn height(&self) -> usize;
+    fn stride(&self) -> usize;
+}
+
+pub trait BitmapDataMut: BitmapData {
+    fn data_mut(&mut self) -> &mut [u8];
+}
+
+#[derive(PartialEq, Clone)]
+pub struct OwnedBitmapData {
+    pub(crate) data: Vec<u8>,
+    pub(crate) width: usize,
+    pub(crate) height: usize,
+    pub(crate) stride: usize,
+}
+
+impl BitmapData for OwnedBitmapData {
+    fn data(&self) -> &[u8] {
+        self.data.as_slice()
+    }
+    fn width(&self) -> usize {
+        self.width
+    }
+    fn height(&self) -> usize {
+        self.height
+    }
+    fn stride(&self) -> usize {
+        self.stride
+    }
+}
+
+impl BitmapDataMut for OwnedBitmapData {
+    fn data_mut(&mut self) -> &mut [u8] {
+        self.data.as_mut_slice()
+    }
+}
+
+#[derive(Clone)]
+pub struct BorrowedBitmapData<'l> {
+    pub(crate) data: &'l [u8],
+    pub(crate) width: usize,
+    pub(crate) height: usize,
+    pub(crate) stride: usize,
+}
+
+impl<'l> BitmapData for BorrowedBitmapData<'l> {
+    fn data(&self) -> &[u8] {
+        self.data
+    }
+    fn width(&self) -> usize {
+        self.width
+    }
+    fn height(&self) -> usize {
+        self.height
+    }
+    fn stride(&self) -> usize {
+        self.stride
+    }
+}
+
+pub struct MutableBitmapData<'l> {
+    pub(crate) data: &'l mut [u8],
+    pub(crate) width: usize,
+    pub(crate) height: usize,
+    pub(crate) stride: usize,
+}
+
+impl<'l> BitmapData for MutableBitmapData<'l> {
+    fn data(&self) -> &[u8] {
+        self.data
+    }
+    fn width(&self) -> usize {
+        self.width
+    }
+    fn height(&self) -> usize {
+        self.height
+    }
+    fn stride(&self) -> usize {
+        self.stride
+    }
+}
+
+impl<'l> BitmapDataMut for MutableBitmapData<'l> {
+    fn data_mut(&mut self) -> &mut [u8] {
+        self.data
+    }
+}
+
+pub trait BitmapAccess<Format: PixelFormatSpec> {
+    fn data(&self) -> &dyn BitmapData;
+    fn width(&self) -> usize;
+    fn height(&self) -> usize;
+
+    fn size(&self) -> math::PixelSize {
+        math::PixelSize::new(self.width() as i32, self.height() as i32)
+    }
+
+    fn row_stride(&self) -> usize;
+}
+
+pub trait BitmapMutAccess<Format: PixelFormatSpec>: BitmapAccess<Format> {
+    fn data_mut(&mut self) -> &mut dyn BitmapDataMut;
+}
+
+/// Owned bitmap with static pixel format information.
+#[derive(Clone, PartialEq)]
+pub struct Bitmap<Format: PixelFormatSpec> {
+    data: OwnedBitmapData,
+    _ghost: std::marker::PhantomData<Format>,
+}
+
+impl<Format: PixelFormatSpec> Bitmap<Format> {
+    pub fn new(width: usize, height: usize) -> Self {
+        Self {
+            data: OwnedBitmapData {
+                data: vec![0; width * height * Format::PIXEL_WIDTH],
+                width,
+                height,
+                stride: width * Format::PIXEL_WIDTH,
+            },
+            _ghost: Default::default(),
+        }
+    }
+
+    pub fn new_from_vec(data: Vec<u8>, width: usize, height: usize, stride: usize) -> Self {
+        Self {
+            data: OwnedBitmapData {
+                data,
+                width,
+                height,
+                stride,
+            },
+            _ghost: Default::default(),
+        }
+    }
+}
+
+impl<Format: PixelFormatSpec> BitmapAccess<Format> for Bitmap<Format> {
+    fn data(&self) -> &dyn BitmapData {
+        &self.data
+    }
+    fn width(&self) -> usize {
+        self.data.width
+    }
+    fn height(&self) -> usize {
+        self.data.height
+    }
+    fn row_stride(&self) -> usize {
+        self.data.stride
+    }
+}
+
+impl<'l, Format: PixelFormatSpec, T: BitmapAccess<Format>> BitmapAccess<Format> for &'l T {
+    fn data(&self) -> &dyn BitmapData {
+        (*self).data()
+    }
+    fn size(&self) -> math::PixelSize {
+        (*self).size()
+    }
+    fn width(&self) -> usize {
+        (*self).width()
+    }
+    fn height(&self) -> usize {
+        (*self).height()
+    }
+    fn row_stride(&self) -> usize {
+        (*self).row_stride()
+    }
+}
+
+impl<Format: PixelFormatSpec> BitmapMutAccess<Format> for Bitmap<Format> {
+    fn data_mut(&mut self) -> &mut dyn BitmapDataMut {
+        &mut self.data
+    }
+}
+
+impl<Format: PixelFormatSpec> std::fmt::Debug for Bitmap<Format> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("Bitmap")
+            .field("width", &self.data.width)
+            .field("height", &self.data.height)
+            .field("stride", &self.data.stride)
+            .finish_non_exhaustive()
+    }
+}
+
+
+/// Borrowed bitmap with static pixel format information.
+#[derive(Clone)]
+pub struct BitmapRef<'l, Format: PixelFormatSpec> {
+    data: BorrowedBitmapData<'l>,
+    _ghost: std::marker::PhantomData<Format>,
+}
+
+impl<'l, Format: PixelFormatSpec> BitmapRef<'l, Format> {
+    /// Create a new bitmap reference using the default row stride.
+    pub fn new(data: &'l [u8], width: usize, height: usize) -> Self {
+        Self {
+            data: BorrowedBitmapData {
+                data,
+                width,
+                height,
+                stride: width * Format::PIXEL_WIDTH,
+            },
+            _ghost: Default::default(),
+        }
+    }
+
+    /// Create a new bitmap reference using a specific row stride.
+    pub fn new_with_stride(data: &'l [u8], width: usize, height: usize, stride: usize) -> Self {
+        Self {
+            data: BorrowedBitmapData {
+                data,
+                width,
+                height,
+                stride,
+            },
+            _ghost: Default::default(),
+        }
+    }
+}
+
+impl<'l, Format: PixelFormatSpec> BitmapAccess<Format> for BitmapRef<'l, Format> {
+    fn data(&self) -> &dyn BitmapData {
+        &self.data
+    }
+    fn width(&self) -> usize {
+        self.data.width
+    }
+    fn height(&self) -> usize {
+        self.data.height
+    }
+    fn row_stride(&self) -> usize {
+        self.data.stride
+    }
+}
+
+/// Mutably borrowed bitmap with static pixel format information.
+pub struct BitmapMut<'l, Format: PixelFormatSpec> {
+    data: MutableBitmapData<'l>,
+    _ghost: std::marker::PhantomData<Format>,
+}
+
+impl<'l, Format: PixelFormatSpec> BitmapMut<'l, Format> {
+    /// Create a new mutable bitmap reference using the default row stride.
+    pub fn new(data: &'l mut [u8], width: usize, height: usize) -> Self {
+        Self {
+            data: MutableBitmapData {
+                data,
+                width,
+                height,
+                stride: width * Format::PIXEL_WIDTH,
+            },
+            _ghost: Default::default(),
+        }
+    }
+
+    /// Create a new mutable bitmap reference using a specific row stride.
+    pub fn new_with_stride(data: &'l mut [u8], width: usize, height: usize, stride: usize) -> Self {
+        Self {
+            data: MutableBitmapData {
+                data,
+                width,
+                height,
+                stride,
+            },
+            _ghost: Default::default(),
+        }
+    }
+    
+}
+
+impl<'l, Format: PixelFormatSpec> BitmapAccess<Format> for BitmapMut<'l, Format> {
+    fn data(&self) -> &dyn BitmapData {
+        &self.data
+    }
+    fn width(&self) -> usize {
+        self.data.width
+    }
+    fn height(&self) -> usize {
+        self.data.height
+    }
+    fn row_stride(&self) -> usize {
+        self.data.stride
+    }
+}
+
+impl<'l, Format: PixelFormatSpec> BitmapMutAccess<Format> for BitmapMut<'l, Format> {
+    fn data_mut(&mut self) -> &mut dyn BitmapDataMut {
+        &mut self.data
+    }
+}

+ 52 - 8
src/colour.rs

@@ -1,8 +1,17 @@
+use crate::formats::PixelFormatSpec;
+
+/// Helper type alias for Americans.
 pub type Color = Colour;
 
+/// Colour specification type.
+///
+/// This type internally stores the colour as RGBA, and each pixel format will use whatever subset
+/// of the colour values are relevant. For example, an `A8` surface will ignore the R/G/B channels.
+/// 'Missing' channels will be given a default value of 255.
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub struct Colour([u8; 4]);
 
+/// Basic colour creation and manipulation functions
 impl Colour {
     pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
         Self([r, g, b, a])
@@ -12,14 +21,6 @@ impl Colour {
         Self([r, g, b, 255])
     }
 
-    pub(crate) fn from_le_bytes(b: &[u8]) -> Self {
-        Self(b[0..4].try_into().unwrap())
-    }
-
-    pub(crate) fn as_le_bytes(&self) -> [u8; 4] {
-        self.0
-    }
-
     pub const fn r(&self) -> u8 {
         self.0[0]
     }
@@ -53,6 +54,49 @@ impl Colour {
     }
 }
 
+/// Generic conversion functions
+impl Colour {
+    pub(crate) fn write_as_bytes<Format: PixelFormatSpec>(&self, to: &mut [u8]) {
+        if let Some(offset) = Format::RED_PACK.index() {
+            to[offset as usize] = self.r();
+        }
+        if let Some(offset) = Format::GREEN_PACK.index() {
+            to[offset as usize] = self.g();
+        }
+        if let Some(offset) = Format::BLUE_PACK.index() {
+            to[offset as usize] = self.b();
+        }
+        if let Some(offset) = Format::ALPHA_PACK.index() {
+            to[offset as usize] = self.a();
+        }
+    }
+
+    pub(crate) fn read_from_bytes<Format: PixelFormatSpec>(from: &[u8]) -> Self {
+        Self::rgba(
+            if let Some(offset) = Format::RED_PACK.index() {
+                from[offset as usize]
+            } else {
+                255
+            },
+            if let Some(offset) = Format::GREEN_PACK.index() {
+                from[offset as usize]
+            } else {
+                255
+            },
+            if let Some(offset) = Format::BLUE_PACK.index() {
+                from[offset as usize]
+            } else {
+                255
+            },
+            if let Some(offset) = Format::ALPHA_PACK.index() {
+                from[offset as usize]
+            } else {
+                255
+            },
+        )
+    }
+}
+
 /// Colour constants
 impl Colour {
     pub const BLACK: Colour = Self::rgba(0, 0, 0, 255);

+ 0 - 90
src/data.rs

@@ -1,90 +0,0 @@
-pub trait BitmapData {
-    fn data(&self) -> &[u8];
-    fn width(&self) -> usize;
-    fn height(&self) -> usize;
-    fn stride(&self) -> usize;
-}
-
-pub trait BitmapDataMut: BitmapData {
-    fn data_mut(&mut self) -> &mut [u8];
-}
-
-#[derive(PartialEq, Clone)]
-pub struct OwnedBitmapData {
-    pub(crate) data: Vec<u8>,
-    pub(crate) width: usize,
-    pub(crate) height: usize,
-    pub(crate) stride: usize,
-}
-
-impl BitmapData for OwnedBitmapData {
-    fn data(&self) -> &[u8] {
-        self.data.as_slice()
-    }
-    fn width(&self) -> usize {
-        self.width
-    }
-    fn height(&self) -> usize {
-        self.height
-    }
-    fn stride(&self) -> usize {
-        self.stride
-    }
-}
-
-impl BitmapDataMut for OwnedBitmapData {
-    fn data_mut(&mut self) -> &mut [u8] {
-        self.data.as_mut_slice()
-    }
-}
-
-#[derive(Clone)]
-pub struct BorrowedBitmapData<'l> {
-    pub(crate) data: &'l [u8],
-    pub(crate) width: usize,
-    pub(crate) height: usize,
-    pub(crate) stride: usize,
-}
-
-impl<'l> BitmapData for BorrowedBitmapData<'l> {
-    fn data(&self) -> &[u8] {
-        self.data
-    }
-    fn width(&self) -> usize {
-        self.width
-    }
-    fn height(&self) -> usize {
-        self.height
-    }
-    fn stride(&self) -> usize {
-        self.stride
-    }
-}
-
-pub struct MutableBitmapData<'l> {
-    pub(crate) data: &'l mut [u8],
-    pub(crate) width: usize,
-    pub(crate) height: usize,
-    pub(crate) stride: usize,
-}
-
-impl<'l> BitmapData for MutableBitmapData<'l> {
-    fn data(&self) -> &[u8] {
-        self.data
-    }
-    fn width(&self) -> usize {
-        self.width
-    }
-    fn height(&self) -> usize {
-        self.height
-    }
-    fn stride(&self) -> usize {
-        self.stride
-    }
-}
-
-impl<'l> BitmapDataMut for MutableBitmapData<'l> {
-    fn data_mut(&mut self) -> &mut [u8] {
-        self.data
-    }
-}

+ 162 - 0
src/dispatch.rs

@@ -0,0 +1,162 @@
+use crate::formats::{self, PixelFormatSpec, NUM_PIXEL_FORMATS};
+use crate::bitmap::{BitmapData, BitmapDataMut};
+use crate::op::{self, UnaryOpSpec};
+
+pub type UnaryFunc<Params> = fn(&mut dyn BitmapDataMut, &Params);
+pub type BinaryFunc<Params> = fn(&mut dyn BitmapDataMut, &dyn BitmapData, &Params);
+
+pub trait GenericUnary<Params> {
+    fn perform<Format: PixelFormatSpec>(write_data: &mut dyn BitmapDataMut, params: &Params);
+
+    fn make_dispatch() -> UnaryDispatch<Params> {
+        UnaryDispatch {
+            op_array: [
+                Self::perform::<formats::A8>,
+                Self::perform::<formats::Abgr32>,
+                Self::perform::<formats::Bgr24>,
+                Self::perform::<formats::Bgr32>,
+                Self::perform::<formats::Rgb24>,
+                Self::perform::<formats::Rgb32>,
+                Self::perform::<formats::Rgba32>,
+            ],
+            changed: Default::default(),
+        }
+    }
+}
+
+pub trait GenericBinary<Params> {
+    fn perform<Format: PixelFormatSpec, ReadFormat: PixelFormatSpec>(write_data: &mut dyn BitmapDataMut, read_data: &dyn BitmapData, params: &Params);
+
+    fn make_dispatch() -> BinaryDispatch<Params> {
+        BinaryDispatch {
+            op_array: [
+                [
+                    Self::perform::<formats::A8, formats::A8>,
+                    Self::perform::<formats::A8, formats::Abgr32>,
+                    Self::perform::<formats::A8, formats::Bgr24>,
+                    Self::perform::<formats::A8, formats::Bgr32>,
+                    Self::perform::<formats::A8, formats::Rgb24>,
+                    Self::perform::<formats::A8, formats::Rgb32>,
+                    Self::perform::<formats::A8, formats::Rgba32>,
+                ],
+                [
+                    Self::perform::<formats::Abgr32, formats::A8>,
+                    Self::perform::<formats::Abgr32, formats::Abgr32>,
+                    Self::perform::<formats::Abgr32, formats::Bgr24>,
+                    Self::perform::<formats::Abgr32, formats::Bgr32>,
+                    Self::perform::<formats::Abgr32, formats::Rgb24>,
+                    Self::perform::<formats::Abgr32, formats::Rgb32>,
+                    Self::perform::<formats::Abgr32, formats::Rgba32>,
+                ],
+                [
+                    Self::perform::<formats::Bgr24, formats::A8>,
+                    Self::perform::<formats::Bgr24, formats::Abgr32>,
+                    Self::perform::<formats::Bgr24, formats::Bgr24>,
+                    Self::perform::<formats::Bgr24, formats::Bgr32>,
+                    Self::perform::<formats::Bgr24, formats::Rgb24>,
+                    Self::perform::<formats::Bgr24, formats::Rgb32>,
+                    Self::perform::<formats::Bgr24, formats::Rgba32>,
+                ],
+                [
+                    Self::perform::<formats::Bgr32, formats::A8>,
+                    Self::perform::<formats::Bgr32, formats::Abgr32>,
+                    Self::perform::<formats::Bgr32, formats::Bgr24>,
+                    Self::perform::<formats::Bgr32, formats::Bgr32>,
+                    Self::perform::<formats::Bgr32, formats::Rgb24>,
+                    Self::perform::<formats::Bgr32, formats::Rgb32>,
+                    Self::perform::<formats::Bgr32, formats::Rgba32>,
+                ],
+                [
+                    Self::perform::<formats::Rgb24, formats::A8>,
+                    Self::perform::<formats::Rgb24, formats::Abgr32>,
+                    Self::perform::<formats::Rgb24, formats::Bgr24>,
+                    Self::perform::<formats::Rgb24, formats::Bgr32>,
+                    Self::perform::<formats::Rgb24, formats::Rgb24>,
+                    Self::perform::<formats::Rgb24, formats::Rgb32>,
+                    Self::perform::<formats::Rgb24, formats::Rgba32>,
+                ],
+                [
+                    Self::perform::<formats::Rgb32, formats::A8>,
+                    Self::perform::<formats::Rgb32, formats::Abgr32>,
+                    Self::perform::<formats::Rgb32, formats::Bgr24>,
+                    Self::perform::<formats::Rgb32, formats::Bgr32>,
+                    Self::perform::<formats::Rgb32, formats::Rgb24>,
+                    Self::perform::<formats::Rgb32, formats::Rgb32>,
+                    Self::perform::<formats::Rgb32, formats::Rgba32>,
+                ],
+                [
+                    Self::perform::<formats::Rgba32, formats::A8>,
+                    Self::perform::<formats::Rgba32, formats::Abgr32>,
+                    Self::perform::<formats::Rgba32, formats::Bgr24>,
+                    Self::perform::<formats::Rgba32, formats::Bgr32>,
+                    Self::perform::<formats::Rgba32, formats::Rgb24>,
+                    Self::perform::<formats::Rgba32, formats::Rgb32>,
+                    Self::perform::<formats::Rgba32, formats::Rgba32>,
+                ],
+            ],
+            changed: Default::default(),
+        }
+    }
+}
+
+pub struct UnaryDispatch<Params> {
+    op_array: [UnaryFunc<Params>; NUM_PIXEL_FORMATS],
+    changed: [bool; NUM_PIXEL_FORMATS],
+}
+
+impl<Params> UnaryDispatch<Params> {
+    pub fn with<Format: PixelFormatSpec>(mut self, specific_imp: UnaryFunc<Params>) -> Self {
+        self.op_array[Format::FORMAT_ENUM as usize] = specific_imp;
+        self.changed[Format::FORMAT_ENUM as usize] = true;
+        self
+    }
+    pub fn with_avx<Format: PixelFormatSpec>(mut self, avx_imp: UnaryFunc<Params>) -> Self {
+        if is_x86_feature_detected!("avx") {
+            self.op_array[Format::FORMAT_ENUM as usize] = avx_imp;
+            self.changed[Format::FORMAT_ENUM as usize] = true;
+            self
+        } else {
+            self
+        }
+    }
+
+    pub fn select<Format: PixelFormatSpec>(&self) -> &UnaryFunc<Params> {
+        &self.op_array[Format::FORMAT_ENUM as usize]
+    }
+}
+
+pub struct BinaryDispatch<Params> {
+    op_array: [[BinaryFunc<Params>; NUM_PIXEL_FORMATS]; NUM_PIXEL_FORMATS],
+    changed: [[bool; NUM_PIXEL_FORMATS]; NUM_PIXEL_FORMATS],
+}
+
+impl<Params> BinaryDispatch<Params> {
+    pub fn with_avx<WriteFormat: PixelFormatSpec, ReadFormat: PixelFormatSpec>(mut self, avx_imp: BinaryFunc<Params>) -> Self {
+        if is_x86_feature_detected!("avx") {
+            self.op_array[WriteFormat::FORMAT_ENUM as usize][ReadFormat::FORMAT_ENUM as usize] = avx_imp;
+            self
+        } else {
+            self
+        }
+    }
+
+    pub fn select<WriteFormat: PixelFormatSpec, ReadFormat: PixelFormatSpec>(&self) -> &BinaryFunc<Params> {
+        &self.op_array[WriteFormat::FORMAT_ENUM as usize][ReadFormat::FORMAT_ENUM as usize]
+    }
+}
+
+pub struct KahloDispatch {
+    pub fill: UnaryDispatch<<op::FillOp as UnaryOpSpec>::Params>,
+}
+
+impl Default for KahloDispatch {
+    fn default() -> Self {
+        Self {
+            fill: op::FillOp::build(),
+        }
+    }
+}
+
+lazy_static::lazy_static! {
+    static ref DISPATCH: KahloDispatch = KahloDispatch::default();
+}

+ 72 - 45
src/formats.rs

@@ -1,28 +1,48 @@
 trait Sealed {}
 
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+#[repr(u8)]
+pub enum PixelFormat {
+    A8,
+    Abgr32,
+    Bgr24,
+    Bgr32,
+    Rgb24,
+    Rgb32,
+    Rgba32,
+}
+
+pub(crate) const NUM_PIXEL_FORMATS: usize = 7;
+pub(crate) const MAX_PIXEL_WIDTH: usize = 4;
+
 #[derive(Clone, PartialEq, Debug, Default)]
-pub struct Mask(std::ops::Range<usize>);
+pub struct PackedLocation(Option<u8>);
 
-impl Mask {
+impl PackedLocation {
     const fn empty() -> Self {
-        Self(0..0)
+        Self(None)
     }
-    const fn select_byte(index: usize) -> Self {
-        Self((index * 8)..(index * 8 + 8))
+    const fn select_byte(index: u8) -> Self {
+        Self(Some(index))
+    }
+
+    pub const fn index(&self) -> Option<u8> {
+        self.0
     }
 }
 
 #[allow(private_bounds)]
-pub trait PixelFormat: Sealed + Clone + Copy {
+pub trait PixelFormatSpec: Sealed + Clone + Copy {
+    const FORMAT_ENUM: PixelFormat;
     const CHANNELS: usize;
     const PIXEL_WIDTH: usize;
 
     const NAME: &'static str;
 
-    const RED_MASK: Mask;
-    const GREEN_MASK: Mask;
-    const BLUE_MASK: Mask;
-    const ALPHA_MASK: Mask;
+    const RED_PACK: PackedLocation;
+    const GREEN_PACK: PackedLocation;
+    const BLUE_PACK: PackedLocation;
+    const ALPHA_PACK: PackedLocation;
 }
 
 //-----------------------------------------------------------------------------
@@ -33,16 +53,17 @@ pub trait PixelFormat: Sealed + Clone + Copy {
 #[derive(Clone, Copy, PartialEq)]
 pub struct A8;
 impl Sealed for A8 {}
-impl PixelFormat for A8 {
+impl PixelFormatSpec for A8 {
+    const FORMAT_ENUM: PixelFormat = PixelFormat::A8;
     const CHANNELS: usize = 1;
     const PIXEL_WIDTH: usize = 1;
 
     const NAME: &'static str = "A8";
 
-    const RED_MASK: Mask = Mask::empty();
-    const GREEN_MASK: Mask = Mask::empty();
-    const BLUE_MASK: Mask = Mask::empty();
-    const ALPHA_MASK: Mask = Mask::select_byte(0);
+    const RED_PACK: PackedLocation = PackedLocation::empty();
+    const GREEN_PACK: PackedLocation = PackedLocation::empty();
+    const BLUE_PACK: PackedLocation = PackedLocation::empty();
+    const ALPHA_PACK: PackedLocation = PackedLocation::select_byte(0);
 }
 
 /// RGBA with 8 bits per channel, stored big-endian
@@ -57,16 +78,17 @@ impl PixelFormat for A8 {
 pub struct Abgr32;
 
 impl Sealed for Abgr32 {}
-impl PixelFormat for Abgr32 {
+impl PixelFormatSpec for Abgr32 {
+    const FORMAT_ENUM: PixelFormat = PixelFormat::Abgr32;
     const CHANNELS: usize = 4;
     const PIXEL_WIDTH: usize = 4;
 
     const NAME: &'static str = "Abgr32";
 
-    const RED_MASK: Mask = Mask::select_byte(3);
-    const GREEN_MASK: Mask = Mask::select_byte(2);
-    const BLUE_MASK: Mask = Mask::select_byte(1);
-    const ALPHA_MASK: Mask = Mask::select_byte(0);
+    const RED_PACK: PackedLocation = PackedLocation::select_byte(3);
+    const GREEN_PACK: PackedLocation = PackedLocation::select_byte(2);
+    const BLUE_PACK: PackedLocation = PackedLocation::select_byte(1);
+    const ALPHA_PACK: PackedLocation = PackedLocation::select_byte(0);
 }
 
 /// 24bpp colour densly packed into 24-bit words
@@ -81,16 +103,17 @@ impl PixelFormat for Abgr32 {
 #[derive(Clone, Copy, PartialEq)]
 pub struct Bgr24;
 impl Sealed for Bgr24 {}
-impl PixelFormat for Bgr24 {
+impl PixelFormatSpec for Bgr24 {
+    const FORMAT_ENUM: PixelFormat = PixelFormat::Bgr24;
     const CHANNELS: usize = 3;
     const PIXEL_WIDTH: usize = 3;
 
     const NAME: &'static str = "Bgr24";
 
-    const RED_MASK: Mask = Mask::select_byte(0);
-    const GREEN_MASK: Mask = Mask::select_byte(1);
-    const BLUE_MASK: Mask = Mask::select_byte(2);
-    const ALPHA_MASK: Mask = Mask::empty();
+    const RED_PACK: PackedLocation = PackedLocation::select_byte(0);
+    const GREEN_PACK: PackedLocation = PackedLocation::select_byte(1);
+    const BLUE_PACK: PackedLocation = PackedLocation::select_byte(2);
+    const ALPHA_PACK: PackedLocation = PackedLocation::empty();
 }
 
 /// 24bpp colour packed into 32-bit words
@@ -105,16 +128,17 @@ impl PixelFormat for Bgr24 {
 #[derive(Clone, Copy, PartialEq)]
 pub struct Bgr32;
 impl Sealed for Bgr32 {}
-impl PixelFormat for Bgr32 {
+impl PixelFormatSpec for Bgr32 {
+    const FORMAT_ENUM: PixelFormat = PixelFormat::Bgr32;
     const CHANNELS: usize = 3;
     const PIXEL_WIDTH: usize = 4;
 
     const NAME: &'static str = "Bgr32";
 
-    const RED_MASK: Mask = Mask::select_byte(2);
-    const GREEN_MASK: Mask = Mask::select_byte(1);
-    const BLUE_MASK: Mask = Mask::select_byte(0);
-    const ALPHA_MASK: Mask = Mask::empty();
+    const RED_PACK: PackedLocation = PackedLocation::select_byte(2);
+    const GREEN_PACK: PackedLocation = PackedLocation::select_byte(1);
+    const BLUE_PACK: PackedLocation = PackedLocation::select_byte(0);
+    const ALPHA_PACK: PackedLocation = PackedLocation::empty();
 }
 
 /// 24bpp colour densly packed into 24-bit words
@@ -129,16 +153,17 @@ impl PixelFormat for Bgr32 {
 #[derive(Clone, Copy, PartialEq)]
 pub struct Rgb24;
 impl Sealed for Rgb24 {}
-impl PixelFormat for Rgb24 {
+impl PixelFormatSpec for Rgb24 {
+    const FORMAT_ENUM: PixelFormat = PixelFormat::Rgb24;
     const CHANNELS: usize = 3;
     const PIXEL_WIDTH: usize = 3;
 
     const NAME: &'static str = "Rgb24";
 
-    const RED_MASK: Mask = Mask::select_byte(2);
-    const GREEN_MASK: Mask = Mask::select_byte(1);
-    const BLUE_MASK: Mask = Mask::select_byte(0);
-    const ALPHA_MASK: Mask = Mask::empty();
+    const RED_PACK: PackedLocation = PackedLocation::select_byte(2);
+    const GREEN_PACK: PackedLocation = PackedLocation::select_byte(1);
+    const BLUE_PACK: PackedLocation = PackedLocation::select_byte(0);
+    const ALPHA_PACK: PackedLocation = PackedLocation::empty();
 }
 
 /// 24bpp colour packed into 32-bit words
@@ -153,16 +178,17 @@ impl PixelFormat for Rgb24 {
 #[derive(Clone, Copy, PartialEq)]
 pub struct Rgb32;
 impl Sealed for Rgb32 {}
-impl PixelFormat for Rgb32 {
+impl PixelFormatSpec for Rgb32 {
+    const FORMAT_ENUM: PixelFormat = PixelFormat::Rgb32;
     const CHANNELS: usize = 3;
     const PIXEL_WIDTH: usize = 4;
 
     const NAME: &'static str = "Rgb32";
 
-    const RED_MASK: Mask = Mask::select_byte(2);
-    const GREEN_MASK: Mask = Mask::select_byte(1);
-    const BLUE_MASK: Mask = Mask::select_byte(0);
-    const ALPHA_MASK: Mask = Mask::empty();
+    const RED_PACK: PackedLocation = PackedLocation::select_byte(2);
+    const GREEN_PACK: PackedLocation = PackedLocation::select_byte(1);
+    const BLUE_PACK: PackedLocation = PackedLocation::select_byte(0);
+    const ALPHA_PACK: PackedLocation = PackedLocation::empty();
 }
 
 /// RGBA with 8 bits per channel, stored little-endian
@@ -177,14 +203,15 @@ impl PixelFormat for Rgb32 {
 pub struct Rgba32;
 
 impl Sealed for Rgba32 {}
-impl PixelFormat for Rgba32 {
+impl PixelFormatSpec for Rgba32 {
+    const FORMAT_ENUM: PixelFormat = PixelFormat::Rgba32;
     const CHANNELS: usize = 4;
     const PIXEL_WIDTH: usize = 4;
 
     const NAME: &'static str = "Rgba32";
 
-    const RED_MASK: Mask = Mask::select_byte(0);
-    const GREEN_MASK: Mask = Mask::select_byte(1);
-    const BLUE_MASK: Mask = Mask::select_byte(2);
-    const ALPHA_MASK: Mask = Mask::select_byte(3);
+    const RED_PACK: PackedLocation = PackedLocation::select_byte(0);
+    const GREEN_PACK: PackedLocation = PackedLocation::select_byte(1);
+    const BLUE_PACK: PackedLocation = PackedLocation::select_byte(2);
+    const ALPHA_PACK: PackedLocation = PackedLocation::select_byte(3);
 }

+ 22 - 209
src/lib.rs

@@ -1,17 +1,32 @@
-mod data;
-mod ops;
+//! kahlo is a software drawing library designed for speed and flexibility.
+//!
+//! The main goal for kahlo is to provide _fast_ drawing of simple 2D shapes and paths onto
+//! multiple surface formats. See the [`formats`] module for the supported formats.
+//!
+//! kahlo will use AVX, if present, but has fallbacks to unoptimized implementations as well. Note
+//! that the unoptimized implementations are mostly present for automated testing and verification
+//! purposes, and are written for clarity and not speed.
+
+mod bitmap;
+// mod ops;
+mod op;
+mod dispatch;
 
 #[cfg(target_endian = "big")]
 std::compile_error!("kahlo only works on little-endian targets!");
 
+/// Colour-related functionality.
 pub mod colour;
+/// Pixel format definitions.
 pub mod formats;
 
+/// Trait re-exports.
 pub mod prelude {
-    pub use super::ops::{AlphaPaintable, Readable, RgbaPaintable, Paintable, Writable};
-    pub use super::{BitmapAccess, BitmapMutAccess};
+    // pub use crate::ops::{AlphaPaintable, Readable, RgbaPaintable, Paintable, Writable};
+    pub use crate::bitmap::{BitmapAccess, BitmapMutAccess};
 }
 
+/// Types for 2D mathematics.
 pub mod math {
     pub struct PixelSpace;
     pub type PixelPoint = euclid::Point2D<i32, PixelSpace>;
@@ -22,211 +37,9 @@ pub mod math {
     pub type PixelSideOffsets = euclid::SideOffsets2D<i32, PixelSpace>;
 }
 
-use formats::PixelFormat;
-
-pub trait BitmapAccess<Format: PixelFormat> {
-    fn data(&self) -> &[u8];
-    fn width(&self) -> usize;
-    fn height(&self) -> usize;
-
-    fn size(&self) -> math::PixelSize {
-        math::PixelSize::new(self.width() as i32, self.height() as i32)
-    }
-
-    fn row_stride(&self) -> usize;
-}
-
-pub trait BitmapMutAccess<Format: PixelFormat>: BitmapAccess<Format> {
-    fn data_mut(&mut self) -> &mut [u8];
-}
-
-/// Owned bitmap with static pixel format information.
-#[derive(Clone, PartialEq)]
-pub struct Bitmap<Format: PixelFormat> {
-    data: data::OwnedBitmapData,
-    _ghost: std::marker::PhantomData<Format>,
-}
-
-impl<Format: PixelFormat> Bitmap<Format> {
-    pub fn new(width: usize, height: usize) -> Self {
-        Self {
-            data: data::OwnedBitmapData {
-                data: vec![0; width * height * Format::PIXEL_WIDTH],
-                width,
-                height,
-                stride: width * Format::PIXEL_WIDTH,
-            },
-            _ghost: Default::default(),
-        }
-    }
-
-    pub fn new_from_vec(data: Vec<u8>, width: usize, height: usize, stride: usize) -> Self {
-        Self {
-            data: data::OwnedBitmapData {
-                data,
-                width,
-                height,
-                stride,
-            },
-            _ghost: Default::default(),
-        }
-    }
-}
-
-impl<Format: PixelFormat> BitmapAccess<Format> for Bitmap<Format> {
-    fn data(&self) -> &[u8] {
-        &self.data.data
-    }
-    fn width(&self) -> usize {
-        self.data.width
-    }
-    fn height(&self) -> usize {
-        self.data.height
-    }
-    fn row_stride(&self) -> usize {
-        self.data.stride
-    }
-}
-
-impl<'l, Format: PixelFormat, T: BitmapAccess<Format>> BitmapAccess<Format> for &'l T {
-    fn data(&self) -> &[u8] {
-        (*self).data()
-    }
-    fn size(&self) -> math::PixelSize {
-        (*self).size()
-    }
-    fn width(&self) -> usize {
-        (*self).width()
-    }
-    fn height(&self) -> usize {
-        (*self).height()
-    }
-    fn row_stride(&self) -> usize {
-        (*self).row_stride()
-    }
-}
-
-impl<Format: PixelFormat> BitmapMutAccess<Format> for Bitmap<Format> {
-    fn data_mut(&mut self) -> &mut [u8] {
-        &mut self.data.data
-    }
-}
-
-impl<Format: PixelFormat> std::fmt::Debug for Bitmap<Format> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("Bitmap")
-            .field("width", &self.data.width)
-            .field("height", &self.data.height)
-            .field("stride", &self.data.stride)
-            .finish_non_exhaustive()
-    }
-}
+pub use bitmap::{Bitmap, BitmapRef, BitmapMut};
 
+/// Helper type alias for a Rgba32 bitmap.
 pub type RgbaBitmap = Bitmap<formats::Rgba32>;
+/// Helper type alias for an A8 bitmap.
 pub type Alphamap = Bitmap<formats::A8>;
-
-/// Borrowed bitmap with static pixel format information.
-#[derive(Clone)]
-pub struct BitmapRef<'l, Format: PixelFormat> {
-    data: data::BorrowedBitmapData<'l>,
-    _ghost: std::marker::PhantomData<Format>,
-}
-
-impl<'l, Format: PixelFormat> BitmapRef<'l, Format> {
-    /// Create a new bitmap reference using the default row stride.
-    pub fn new(data: &'l [u8], width: usize, height: usize) -> Self {
-        Self {
-            data: data::BorrowedBitmapData {
-                data,
-                width,
-                height,
-                stride: width * Format::PIXEL_WIDTH,
-            },
-            _ghost: Default::default(),
-        }
-    }
-
-    /// Create a new bitmap reference using a specific row stride.
-    pub fn new_with_stride(data: &'l [u8], width: usize, height: usize, stride: usize) -> Self {
-        Self {
-            data: data::BorrowedBitmapData {
-                data,
-                width,
-                height,
-                stride,
-            },
-            _ghost: Default::default(),
-        }
-    }
-}
-
-impl<'l, Format: PixelFormat> BitmapAccess<Format> for BitmapRef<'l, Format> {
-    fn data(&self) -> &[u8] {
-        &self.data.data
-    }
-    fn width(&self) -> usize {
-        self.data.width
-    }
-    fn height(&self) -> usize {
-        self.data.height
-    }
-    fn row_stride(&self) -> usize {
-        self.data.stride
-    }
-}
-
-/// Mutably borrowed bitmap with static pixel format information.
-pub struct BitmapMut<'l, Format: PixelFormat> {
-    data: data::MutableBitmapData<'l>,
-    _ghost: std::marker::PhantomData<Format>,
-}
-
-impl<'l, Format: PixelFormat> BitmapMut<'l, Format> {
-    /// Create a new mutable bitmap reference using the default row stride.
-    pub fn new(data: &'l mut [u8], width: usize, height: usize) -> Self {
-        Self {
-            data: data::MutableBitmapData {
-                data,
-                width,
-                height,
-                stride: width * Format::PIXEL_WIDTH,
-            },
-            _ghost: Default::default(),
-        }
-    }
-
-    /// Create a new mutable bitmap reference using a specific row stride.
-    pub fn new_with_stride(data: &'l mut [u8], width: usize, height: usize, stride: usize) -> Self {
-        Self {
-            data: data::MutableBitmapData {
-                data,
-                width,
-                height,
-                stride,
-            },
-            _ghost: Default::default(),
-        }
-    }
-    
-}
-
-impl<'l, Format: PixelFormat> BitmapAccess<Format> for BitmapMut<'l, Format> {
-    fn data(&self) -> &[u8] {
-        &self.data.data
-    }
-    fn width(&self) -> usize {
-        self.data.width
-    }
-    fn height(&self) -> usize {
-        self.data.height
-    }
-    fn row_stride(&self) -> usize {
-        self.data.stride
-    }
-}
-
-impl<'l, Format: PixelFormat> BitmapMutAccess<Format> for BitmapMut<'l, Format> {
-    fn data_mut(&mut self) -> &mut [u8] {
-        &mut self.data.data
-    }
-}

+ 10 - 0
src/op.rs

@@ -0,0 +1,10 @@
+mod fill;
+pub use fill::FillOp;
+
+use crate::dispatch::UnaryDispatch;
+
+pub trait UnaryOpSpec {
+    type Params;
+
+    fn build() -> UnaryDispatch<Self::Params>;
+}

+ 83 - 0
src/op/fill.rs

@@ -0,0 +1,83 @@
+use crate::{dispatch::GenericUnary, colour::Colour, bitmap::BitmapDataMut, formats::{MAX_PIXEL_WIDTH, A8}};
+
+use super::UnaryOpSpec;
+
+pub struct FillOp;
+
+impl UnaryOpSpec for FillOp {
+    type Params = Colour;
+    fn build() -> crate::dispatch::UnaryDispatch<Self::Params> {
+        FillOp::make_dispatch()
+            .with::<A8>(fill_a8)
+    }
+}
+
+fn fill_a8(target: &mut dyn BitmapDataMut, with: &Colour) {
+    let height = target.height();
+    let row_width = target.width();
+    let stride = target.stride();
+    let data = target.data_mut();
+
+    let alpha = with.a();
+
+    // optimization: can we fill the entire thing in one go?
+    if stride == row_width {
+        data[0..(stride*height)].fill(alpha);
+        return;
+    }
+
+    for y in 0..height {
+        let yoff = y * stride;
+
+        data[yoff..(yoff+row_width)].fill(alpha);
+    }
+}
+
+fn fill_helper<const WIDTH: usize>(target: &mut dyn BitmapDataMut, pattern: &[u8]) {
+    let width = target.width();
+    let row_width = width * WIDTH;
+    let stride = target.stride();
+    let height = target.height();
+    let target_data = target.data_mut();
+
+    // first, fill in the first row
+    let mut filled = WIDTH;
+    target_data[0..WIDTH].copy_from_slice(pattern);
+    while filled < row_width {
+        let to_fill = row_width - filled;
+
+        let to_copy = to_fill.min(filled);
+        target_data.copy_within(0..to_copy, filled);
+        filled += to_copy;
+    }
+
+    // then copy the first row to the rest
+    for y in 1..height {
+        let yoff = y * stride;
+        target_data.copy_within(0..row_width, yoff);
+    }
+}
+
+impl<'l> GenericUnary<Colour> for FillOp {
+    fn perform<Format: crate::formats::PixelFormatSpec>(write_data: &mut dyn BitmapDataMut, params: &Colour) {
+        if write_data.width() == 0 || write_data.height() == 0 { return }
+
+        let mut cdata = [0u8; MAX_PIXEL_WIDTH];
+        params.write_as_bytes::<Format>(&mut cdata);
+
+        let cdata = &cdata[0..Format::PIXEL_WIDTH];
+
+        if Format::PIXEL_WIDTH == 1 {
+            fill_helper::<1>(write_data, cdata);
+        } else if Format::PIXEL_WIDTH == 2 {
+            fill_helper::<2>(write_data, cdata);
+        } else if Format::PIXEL_WIDTH == 3 {
+            fill_helper::<3>(write_data, cdata);
+        } else if Format::PIXEL_WIDTH == 4 {
+            fill_helper::<4>(write_data, cdata);
+        } else { unreachable!() }
+    }
+}
+
+/*#[bench]
+fn */

+ 7 - 40
src/ops.rs

@@ -1,23 +1,24 @@
 use crate::{
+    prelude::*,
     colour::{BlendMode, Colour},
-    formats::{Rgba32, A8},
-    math, BitmapAccess, BitmapMutAccess, PixelFormat,
+    formats::{PixelFormatSpec, Rgba32, A8},
+    math
 };
 
 mod helpers;
 mod impls;
 
-pub trait Readable<PF: PixelFormat> {
+pub trait Readable<PF: PixelFormatSpec> {
     type ReadResult: Clone;
     fn get_pixel(&self, x: usize, y: usize) -> Self::ReadResult;
 }
 
-pub trait Writable<PF: PixelFormat>: Readable<PF> {
+pub trait Writable<PF: PixelFormatSpec>: Readable<PF> {
     fn set_pixel(&mut self, x: usize, y: usize, to: Self::ReadResult);
 }
 
 /// Generic image manipulation functionality
-pub trait Paintable<PF: PixelFormat>: BitmapMutAccess<PF> + Writable<PF> {
+pub trait Paintable<PF: PixelFormatSpec>: BitmapMutAccess<PF> + Writable<PF> {
     fn fill(&mut self, with: Self::ReadResult) {
         for y in 0..self.height() {
             for x in 0..self.width() {
@@ -72,44 +73,10 @@ pub trait Paintable<PF: PixelFormat>: BitmapMutAccess<PF> + Writable<PF> {
     }
 }
 
-impl<PF: PixelFormat, T: BitmapMutAccess<PF> + Writable<PF>> Paintable<PF> for T {}
+impl<PF: PixelFormatSpec, T: BitmapMutAccess<PF> + Writable<PF>> Paintable<PF> for T {}
 
 /// A8-specific painting trait.
 pub trait AlphaPaintable: BitmapMutAccess<A8> + Writable<A8, ReadResult = u8> {
-    /*
-    fn fill(&mut self, with: u8) {
-        self.data_mut().fill(with);
-    }
-
-    fn copy_from(
-        &mut self,
-        amap: &impl BitmapAccess<A8>,
-        amap_region: &math::PixelBox,
-        dst: &math::PixelPoint,
-    ) {
-        let Some((write_region, read_region)) = helpers::clip_to(
-            self.size(),
-            *dst,
-            amap.size(),
-            amap_region.min,
-            amap_region.size(),
-        ) else {
-            return;
-        };
-
-        let row_width = write_region.width() as usize * A8::PIXEL_WIDTH;
-        for y in 0..write_region.height() {
-            let write_offset = ((y + write_region.min.y) * self.row_stride() as i32) as usize;
-            let read_offset = ((y + read_region.min.y) * amap.row_stride() as i32) as usize;
-
-            let write_offset = write_offset + write_region.min.x as usize;
-            let read_offset = read_offset + read_region.min.x as usize;
-
-            self.data_mut()[write_offset..(write_offset + row_width)]
-                .copy_from_slice(&amap.data()[read_offset..(read_offset + row_width)]);
-        }
-    }
-    */
 }
 
 impl<T: BitmapMutAccess<A8>> AlphaPaintable for T {}

+ 1 - 1
src/ops/impls.rs

@@ -1,5 +1,5 @@
 use super::{Readable, Writable};
-use crate::{colour::Colour, formats::*, BitmapAccess, BitmapMutAccess};
+use crate::{colour::Colour, formats::*, prelude::*};
 
 impl<T: BitmapAccess<A8>> Readable<A8> for T {
     type ReadResult = u8;

+ 1 - 1
tests/common/mod.rs

@@ -1,4 +1,4 @@
-use kahlo::{Bitmap, BitmapAccess};
+use kahlo::{Bitmap, prelude::*};
 
 pub fn load_test_resource(name: &'static str) -> Bitmap<kahlo::formats::Rgba32> {
     let mut path = std::path::PathBuf::new();