lumen-engine 0.2.2

Core composition model and rendering orchestration for Lumen.
Documentation
use bytemuck::Zeroable;

use super::types::{
    GradientInterpolation, GradientPaint, GradientSpread, GradientUnits, MAX_GRADIENT_STOPS,
    PaintKind,
};

#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct GpuPaint {
    pub(crate) colors: [[f32; 4]; MAX_GRADIENT_STOPS],
    pub(crate) offsets: [[f32; 4]; MAX_GRADIENT_STOPS],
    pub(crate) start: [f32; 2],
    pub(crate) end: [f32; 2],
    pub(crate) center: [f32; 2],
    pub(crate) radius: [f32; 2],
    pub(crate) angle: f32,
    pub(crate) kind: u32,
    pub(crate) units: u32,
    pub(crate) spread: u32,
    pub(crate) interpolation: u32,
    pub(crate) stop_count: u32,
    pub(crate) _pad: [u32; 2],
}

impl GpuPaint {
    pub(crate) fn solid(color: [u8; 4]) -> Self {
        let mut paint = Self::zeroed();
        paint.colors[0] = rgba8_to_f32(color);
        paint.offsets[0][0] = 0.0;
        paint.stop_count = 1;
        paint
    }
}

pub(crate) fn gradient_to_gpu(gradient: &GradientPaint, fallback: [u8; 4]) -> GpuPaint {
    let mut paint = GpuPaint::solid(fallback);
    paint.kind = match gradient.kind {
        PaintKind::LinearGradient => 1,
        PaintKind::RadialGradient => 2,
        PaintKind::ConicGradient => 3,
    };
    paint.units = match gradient.units {
        GradientUnits::ObjectBoundingBox => 0,
        GradientUnits::UserSpace => 1,
    };
    paint.spread = match gradient.spread {
        GradientSpread::Pad => 0,
        GradientSpread::Repeat => 1,
        GradientSpread::Reflect => 2,
    };
    paint.interpolation = match gradient.interpolation {
        GradientInterpolation::Srgb => 0,
        GradientInterpolation::LinearSrgb => 1,
    };
    paint.start = gradient.start;
    paint.end = gradient.end;
    paint.center = gradient.center;
    paint.radius = gradient.radius;
    paint.angle = gradient.angle;
    if gradient.stops.len() > MAX_GRADIENT_STOPS {
        tracing::warn!(
            target: "lumen_render",
            stop_count = gradient.stops.len(),
            max_stop_count = MAX_GRADIENT_STOPS,
            "truncating gradient stops for gpu paint"
        );
    }
    for (index, stop) in gradient.stops.iter().take(MAX_GRADIENT_STOPS).enumerate() {
        paint.offsets[index][0] = stop.offset.clamp(0.0, 1.0);
        paint.colors[index] = rgba8_to_f32(stop.color);
        paint.stop_count = index as u32 + 1;
    }
    paint
}

fn rgba8_to_f32(color: [u8; 4]) -> [f32; 4] {
    [
        f32::from(color[0]) / 255.0,
        f32::from(color[1]) / 255.0,
        f32::from(color[2]) / 255.0,
        f32::from(color[3]) / 255.0,
    ]
}

#[cfg(test)]
pub(crate) use gradient_to_gpu as test_gradient_to_gpu;