Skip to main content

srgb/
srgb.rs

1// Copyright 2024 the Fearless_SIMD Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Converts a single RGBA pixel from linear RGB to sRGB.
5//!
6//! This example demonstrates:
7//! - processing data in fixed-size chunks
8//! - safely dropping down to platform-specific intrinsics
9//!
10//! It follows the usual Fearless SIMD structure:
11//!
12//! - write the main computation as an `#[inline(always)]` function generic over
13//!   [`Simd`];
14//! - use [`dispatch!`] at the non-SIMD boundary to run it with the best
15//!   available target features;
16//! - drop down to [`kernel!`](fearless_simd::kernel) when a small part of the
17//!   computation needs a target-specific intrinsic.
18//!
19//! The RGB channels are converted with portable SIMD operations. The alpha
20//! channel is copied unchanged, using an architecture-specific lane-copy
21//! intrinsic if one is available and a scalar fallback otherwise.
22
23use fearless_simd::{Level, dispatch, f32x4, prelude::*};
24
25#[cfg(target_arch = "aarch64")]
26use core::arch::aarch64::{float32x4_t, vcopyq_laneq_f32};
27#[cfg(target_arch = "x86")]
28use core::arch::x86::{__m128, _mm_blend_ps};
29#[cfg(target_arch = "x86_64")]
30use core::arch::x86_64::{__m128, _mm_blend_ps};
31
32fearless_simd::kernel!(
33    /// Copy the alpha lane on AArch64 using a NEON lane-copy intrinsic.
34    #[inline]
35    fn copy_alpha_neon(neon: Neon, a: float32x4_t, b: float32x4_t) -> float32x4_t {
36        vcopyq_laneq_f32::<3, 3>(a, b)
37    }
38);
39
40fearless_simd::kernel!(
41    /// Copy the alpha lane on x86 using the SSE4.2 token to enable SSE4.1 blend instructions.
42    #[inline]
43    fn copy_alpha_sse4_2(sse4_2: Sse4_2, a: __m128, b: __m128) -> __m128 {
44        _mm_blend_ps::<8>(a, b)
45    }
46);
47
48/// Return `a` with its alpha channel replaced by `b`'s alpha channel.
49///
50/// This helper shows how portable SIMD code can opportunistically call
51/// target-specific kernels while still providing a fallback for every backend.
52#[inline(always)] // or #[simd], either works
53fn copy_alpha<S: Simd>(a: f32x4<S>, b: f32x4<S>) -> f32x4<S> {
54    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
55    if let Some(sse4_2) = a.simd.level().as_sse4_2() {
56        return copy_alpha_sse4_2(sse4_2, a.into(), b.into()).simd_into(a.simd);
57    }
58
59    #[cfg(target_arch = "aarch64")]
60    if let Some(neon) = a.simd.level().as_neon() {
61        return copy_alpha_neon(neon, a.into(), b.into()).simd_into(a.simd);
62    }
63
64    let mut result = a;
65    result[3] = b[3];
66    result
67}
68
69/// Approximate the linear-RGB to sRGB transfer curve for RGB, preserving alpha.
70#[inline(always)] // or #[simd], either works
71fn to_srgb<S: Simd>(simd: S, rgba: [f32; 4]) -> [f32; 4] {
72    let v: f32x4<S> = rgba.simd_into(simd);
73    let vabs = v.abs();
74    let x = vabs - 5.358_626_4e-4;
75    let x2 = x * x;
76    let even1 = x * -9.127_959e-1 + -2.881_431_4e-2;
77    let even2 = x2 * -7.291_929e-1 + even1;
78    let odd1 = x * 1.061_331_7 + 1.401_945_4;
79    let odd2 = x2 * 2.077_583e-1 + odd1;
80    let poly = odd2 * x.sqrt() + even2;
81    let lin = vabs * 12.92;
82    let z = vabs.simd_gt(0.0031308).select(poly, lin);
83    let z_signed = z.copysign(v);
84    let result = copy_alpha(z_signed, v);
85    result.into()
86}
87
88fn main() {
89    let level = Level::new();
90    let rgba = [0.1, -0.2, 0.001, 0.4];
91    let srgb = dispatch!(level, simd=> to_srgb(simd, rgba));
92    println!("{srgb:?}");
93}