Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions canvas/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ categories = ["multimedia::images"]
[dependencies]
image-texel = { path = "../texel", version = "0.5.0" }
bytemuck = "1.1"
libm = { version = "0.2", default-features = false, features = ["arch"] }

[features]
# Use runtime feature detection on x86 and x86_64 targets.
runtime-features = []

[dev-dependencies]
brunch = "0.6.1"
Expand Down
92 changes: 77 additions & 15 deletions canvas/src/arch.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#![allow(unsafe_code)]
// May be unused if no architecture features are detected at compile time or runtime.
#[allow(unused_imports)]
use core::mem::transmute;

// For when we want to make sure we have a texel at compile time based on bytemuck.
Expand All @@ -12,8 +14,12 @@ macro_rules! expect_texel {
};
}

// May be unused if no architecture features are detected at compile time or runtime.
#[allow(dead_code)]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod x86_avx2;
// May be unused if no architecture features are detected at compile time or runtime.
#[allow(dead_code)]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod x86_ssse3;

Expand All @@ -30,29 +36,85 @@ pub(crate) struct ShuffleOps {

impl ShuffleOps {
/// FIXME(perf): implement and choose arch-specific shuffles.
// May be unused if no architecture features are detected at compile time or runtime.
#[allow(unused_mut)]
pub fn with_arch(mut self) -> Self {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
self = self.with_x86();
}

self
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
// May be unused if no architecture features are detected at compile time or runtime.
#[allow(unused_mut)]
fn with_x86(mut self) -> Self {
#[cfg(target_feature = "ssse3")]
// SAFETY: `ssse3` detected at compile time
unsafe {
self = self.with_x86_ssse3();
}

#[cfg(not(target_feature = "ssse3"))]
#[cfg(feature = "runtime-features")]
if std::is_x86_feature_detected!("ssse3") {
self.shuffle_u8x4 = unsafe {
transmute::<unsafe fn(&mut [[u8; 4]], [u8; 4]), _>(x86_ssse3::shuffle_u8x4)
};
self.shuffle_u16x4 = unsafe {
transmute::<unsafe fn(&mut [[u16; 4]], [u8; 4]), _>(x86_ssse3::shuffle_u16x4)
};
// SAFETY: `ssse3` detected at runtime
unsafe {
self = self.with_x86_ssse3();
}
}

#[cfg(target_feature = "avx2")]
// SAFETY: `avx2` detected at compile time
unsafe {
self = self.with_x86_avx2();
}

#[cfg(not(target_feature = "avx2"))]
#[cfg(feature = "runtime-features")]
if std::is_x86_feature_detected!("avx2") {
// SAFETY: `avx2` detected at runtime
unsafe {
self = self.with_x86_avx2();
}
}

self
}

/// # Safety
///
/// Must only be used when the `ssse3` feature is available.
// May be unused if no architecture features are detected at compile time or runtime.
#[allow(dead_code)]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe fn with_x86_ssse3(mut self) -> Self {
self.shuffle_u8x4 =
unsafe { transmute::<unsafe fn(&mut [[u8; 4]], [u8; 4]), _>(x86_ssse3::shuffle_u8x4) };
self.shuffle_u16x4 = unsafe {
transmute::<unsafe fn(&mut [[u16; 4]], [u8; 4]), _>(x86_ssse3::shuffle_u16x4)
};

self
}

/// # Safety
///
/// Must only be used when the `avx2` feature is available.
// May be unused if no architecture features are detected at compile time or runtime.
#[allow(dead_code)]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe fn with_x86_avx2(mut self) -> Self {
// Note: On Ivy Bridge these have the same *throughput* of 256bit-per-cycle as their SSSE3
// equivalents until Icelake. With Icelake they are twice as fast at 512bit-per-cycle.
// Therefore, we don't select them until we find a way to predict/select this.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if std::is_x86_feature_detected!("avx2") {
self.shuffle_u8x4 = unsafe {
transmute::<unsafe fn(&mut [[u8; 4]], [u8; 4]), _>(x86_avx2::shuffle_u8x4)
};
self.shuffle_u16x4 = unsafe {
transmute::<unsafe fn(&mut [[u16; 4]], [u8; 4]), _>(x86_avx2::shuffle_u16x4)
};
}

self.shuffle_u8x4 =
unsafe { transmute::<unsafe fn(&mut [[u8; 4]], [u8; 4]), _>(x86_avx2::shuffle_u8x4) };
self.shuffle_u16x4 =
unsafe { transmute::<unsafe fn(&mut [[u16; 4]], [u8; 4]), _>(x86_avx2::shuffle_u16x4) };

self
}
Expand Down
4 changes: 3 additions & 1 deletion canvas/src/bits.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::layout::{SampleBits, SampleParts};
use image_texel::{AsTexel, Texel};
#[expect(unused_imports)]
use image_texel::AsTexel;
use image_texel::Texel;

/// Specifies which bits a channel comes from, within a `TexelKind` aggregate.
#[derive(Clone, Copy, Debug)]
Expand Down
3 changes: 2 additions & 1 deletion canvas/src/color/oklab.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::color_matrix::ColMatrix;
use crate::math::powf;

const M1: ColMatrix = ColMatrix([
[0.8189330101, 0.0329845436, 0.0482003018],
Expand Down Expand Up @@ -94,7 +95,7 @@ pub(crate) fn f_lms_inv(lms: [f32; 3]) -> [f32; 3] {
}

fn pow([a, b, c]: [f32; 3], exp: f32) -> [f32; 3] {
[a.powf(exp), b.powf(exp), c.powf(exp)]
[powf(a, exp), powf(b, exp), powf(c, exp)]
}

fn copysign([a, b, c]: [f32; 3], [sa, sb, sc]: [f32; 3]) -> [f32; 3] {
Expand Down
5 changes: 3 additions & 2 deletions canvas/src/color/srlab2.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::color::Whitepoint;
use crate::color_matrix::ColMatrix;
use crate::math::powf;

#[rustfmt::skip]
const M_CAT02: ColMatrix = ColMatrix([
Expand Down Expand Up @@ -119,7 +120,7 @@ fn non_linearity(lms: [f32; 3]) -> [f32; 3] {
// Limited to 0.08 precisely
v * 24389.0 / 2700.0
} else {
1.16 * v.powf(1.0 / 3.0) - 0.16
1.16 * powf(v, 1.0 / 3.0) - 0.16
}
}

Expand All @@ -131,7 +132,7 @@ fn non_linearity_inv(lms: [f32; 3]) -> [f32; 3] {
if v.abs() < 0.08 {
v * 2700.0 / 24389.0
} else {
((v + 0.16) / 1.16).powf(3.0)
powf((v + 0.16) / 1.16, 3.0)
}
}

Expand Down
3 changes: 2 additions & 1 deletion canvas/src/color/transfer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// To emulate the syntax used in GLSL more closely.
#[inline]
fn pow(base: f32, exp: f32) -> f32 {
base.powf(exp)
crate::math::powf(base, exp)
}

pub fn transfer_oe_bt709(val: f32) -> f32 {
Expand Down Expand Up @@ -143,6 +143,7 @@ pub fn transfer_display_scene_smpte2084(val: f32) -> f32 {
pub fn transfer_oe_smpte2084(val: f32) -> f32 {
transfer_eo_inv_smpte2084(transfer_scene_display_smpte2084(val))
}
#[expect(dead_code)]
pub fn transfer_oe_inv_smpte2084(val: f32) -> f32 {
transfer_display_scene_smpte2084(transfer_eo_smpte2084(val))
}
Expand Down
2 changes: 2 additions & 0 deletions canvas/src/color/yuv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ impl Differencing {
struct Bt407MPal;
struct Bt407MPalPrecise;
struct Pal525;
#[expect(dead_code)]
struct Pal625;
struct Bt601;
struct Bt601Quantized;
Expand All @@ -118,6 +119,7 @@ struct Bt2020;
struct Bt2100;
struct YDbDr;
struct YCoCg;
#[expect(dead_code)]
struct YCoCgR;

// We derive the coefficients from scratch, from their definition.
Expand Down
5 changes: 5 additions & 0 deletions canvas/src/frame.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
//! A byte-buffer based image descriptor.

use alloc::borrow::ToOwned;
use alloc::vec::Vec;

use image_texel::image::{ImageMut, ImageRef};
use image_texel::Image;

Expand All @@ -18,6 +22,7 @@ pub struct Plane {
inner: Image<PlaneBytes>,
}

#[expect(dead_code)]
#[doc(hidden)]
#[deprecated = "Use BytePlaneRef"]
pub type BytePlane<'data> = BytePlaneRef<'data>;
Expand Down
5 changes: 4 additions & 1 deletion canvas/src/layout.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
//! Defines layout and buffer of our images.
use crate::color::{Color, ColorChannel, ColorChannelModel};

use alloc::boxed::Box;

use image_texel::image::{Coord, ImageRef};
use image_texel::layout::{
Decay, Layout as ImageLayout, MatrixBytes, Raster, SliceLayout, StrideSpec, StridedBytes,
Strides, TexelLayout,
};

use crate::color::{Color, ColorChannel, ColorChannelModel};
use crate::shader::ChunkSpec;

/// The byte layout of a buffer.
Expand Down Expand Up @@ -376,6 +378,7 @@ pub enum SampleBits {

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum BitEncoding {
#[expect(dead_code)]
Opaque,
UInt,
Int,
Expand Down
10 changes: 10 additions & 0 deletions canvas/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@
// Deny, not forbid, unsafe code. In `arch` module we have inherently unsafe code, for the moment.
// Maybe at a future point we gain some possibility to write such code safely.
#![deny(unsafe_code)]
// Be std for doctests, avoids a weird warning about missing allocator.
#![cfg_attr(not(doctest), no_std)]

#[cfg(feature = "runtime-features")]
extern crate std;

#[macro_use]
extern crate alloc;

mod arch;
mod bits;
Expand All @@ -65,6 +73,8 @@ mod color_matrix;
mod frame;
/// The layout implementation, builders, descriptors.
pub mod layout;
/// Core maths operations.
mod math;
/// Conversion operation.
mod shader;

Expand Down
5 changes: 5 additions & 0 deletions canvas/src/math.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// Equivalent to `f32::powf` but suitable on `no_std`.
#[inline]
pub(crate) fn powf(base: f32, exp: f32) -> f32 {
libm::powf(base, exp)
}
6 changes: 5 additions & 1 deletion canvas/src/shader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//!
//! Takes quite a lot of inspiration from how GPUs work. We have a primitive sampler unit, a
//! fragment unit, and pipeline multiple texels in parallel.
use alloc::vec::Vec;
use core::ops::Range;
use image_texel::image::{ImageMut, ImageRef};
use image_texel::{AsTexel, Texel, TexelBuffer};
Expand Down Expand Up @@ -1231,6 +1232,7 @@ impl CommonPixel {
)
}

#[expect(dead_code)]
fn expand_yuy2(info: &Info, in_texel: &TexelBuffer, pixel_buf: &mut TexelBuffer) {
struct ExpandYuy2;

Expand Down Expand Up @@ -1482,7 +1484,9 @@ impl CommonPixel {
// FIXME: do the transform u32::from_ne_bytes(x.as_ne_bytes()) when appropriate.
join_fn: |num, bits, idx| {
let max_val = bits.mask();
let raw = (num[(idx & 0x3) as usize] * max_val as f32).round() as u32;
// Equivalent to `x.round() as u32` for positive-normal f32
let round = |x| (x + 0.5) as u32;
let raw = round(num[(idx & 0x3) as usize] * max_val as f32);
raw.min(max_val)
},
bits,
Expand Down
3 changes: 3 additions & 0 deletions drm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
//! pixel matrix. Then some of those formats map cleanly to planes of color information that can be
//! viewed as a matrix with strides, which finally enables useful operations such as
//! initialization.
// Be std for doctests, avoids a weird warning about missing allocator.
#![cfg_attr(not(doctest), no_std)]

use canvas::{layout, texels};
use core::convert::TryFrom;
use core::fmt;
Expand Down
1 change: 1 addition & 0 deletions texel/src/image/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ impl<B: BufferLike, L> RawImage<B, L> {

/// Methods specifically with a dynamic layout.
impl<B> RawImage<B, DynLayout> {
#[expect(dead_code)]
Copy link
Member

@197g 197g May 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I don't expect this, this is an artifact of ongoing development work and should get linted as a nit that it needs to go, or be used (before the next release). Same with the Yuv420p` layout and so on. Rather have those as a separate PR (please amend the commit instead of just adding one on top, to avoid merge conflicts with my branch that does touch some of these lints).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried editing that particular commit directly but I think that didn't work as you'd like. If there's any issues with the history as-is just let me know and I can re-open this PR on a clean branch instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can also squash-and-merge so no worries, it'd just have been neater.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if you're not comfortable with that workflow in git before you can also just add commits and let me know to squash. We lose the history of individual commits but should be the same result.

pub(crate) fn try_from_dynamic<Other>(self, layout: Other) -> Result<RawImage<B, Other>, Self>
where
Other: Into<DynLayout> + Clone,
Expand Down
1 change: 1 addition & 0 deletions texel/src/layout/upsampling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub(crate) struct Yuv420p {
}

impl Yuv420p {
#[expect(dead_code)]
pub fn from_width_height(channel: TexelLayout, width: u32, height: u32) -> Option<Self> {
use core::convert::TryFrom;
if width % 2 != 0 || height % 2 != 0 {
Expand Down
Loading