scena 1.7.2

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
use std::collections::BTreeMap;

use crate::scene::recipe::SceneRecipeQualityReflectionV1;

use super::metrics::frame_metrics;
use super::types::{self, RenderQualityCheckV1, RenderQualityRegion, RenderQualityStatusV1};

pub fn evaluate_reflection_region_quality(
    id: &str,
    rgba8: &[u8],
    width: u32,
    height: u32,
    region: RenderQualityRegion,
    expectation: SceneRecipeQualityReflectionV1,
) -> Vec<RenderQualityCheckV1> {
    let metrics = frame_metrics(rgba8, width, height, region);
    let min_luminance_range = expectation.min_luminance_range.unwrap_or(0.16) as f32;
    let min_sobel_energy = expectation.min_sobel_energy.unwrap_or(0.03) as f32;
    let min_chroma_range = expectation.min_chroma_range.unwrap_or(0.08) as f32;
    let max_firefly_fraction = expectation.max_firefly_fraction.unwrap_or(0.01) as f32;
    let chroma_range = reflection_chroma_range(rgba8, width, height, region);
    let firefly_fraction = reflection_firefly_fraction(rgba8, width, height, region);
    let chrome_read = reflection_chrome_read_metrics(rgba8, width, height, region);
    let mut checks = Vec::new();
    if metrics.luminance_range < min_luminance_range
        || metrics.sobel_energy < min_sobel_energy
        || chroma_range < min_chroma_range
    {
        checks.push(RenderQualityCheckV1 {
            id: id.to_owned(),
            code: "reflection_structure_missing".to_owned(),
            status: RenderQualityStatusV1::Failed,
            severity: "error".to_owned(),
            region: region.to_report(),
            observed: BTreeMap::from([
                ("chroma_range".to_owned(), types::round3(chroma_range)),
                ("luminance_range".to_owned(), metrics.luminance_range),
                ("sobel_energy".to_owned(), metrics.sobel_energy),
            ]),
            threshold: BTreeMap::from([
                ("min_chroma_range".to_owned(), min_chroma_range),
                ("min_luminance_range".to_owned(), min_luminance_range),
                ("min_sobel_energy".to_owned(), min_sobel_energy),
            ]),
            fix_hint: "enable a reflective floor or SSR/IBL reflection path that shows structured subject or environment detail on the reflective surface".to_owned(),
        });
    }
    if firefly_fraction > max_firefly_fraction {
        checks.push(RenderQualityCheckV1 {
            id: id.to_owned(),
            code: "reflection_firefly_outliers".to_owned(),
            status: RenderQualityStatusV1::Failed,
            severity: "error".to_owned(),
            region: region.to_report(),
            observed: BTreeMap::from([(
                "firefly_fraction".to_owned(),
                types::round3(firefly_fraction),
            )]),
            threshold: BTreeMap::from([(
                "max_firefly_fraction".to_owned(),
                max_firefly_fraction,
            )]),
            fix_hint: "increase IBL prefilter quality or use source-mip importance sampling so tiny HDR emitters blur into reflection mips instead of isolated bright specks".to_owned(),
        });
    }
    let min_bright_fraction = expectation.min_bright_fraction.map(|value| value as f32);
    let min_dark_fraction = expectation.min_dark_fraction.map(|value| value as f32);
    if min_bright_fraction.is_some_and(|threshold| chrome_read.bright_fraction < threshold)
        || min_dark_fraction.is_some_and(|threshold| chrome_read.dark_fraction < threshold)
    {
        let mut threshold = BTreeMap::new();
        if let Some(value) = min_bright_fraction {
            threshold.insert("min_bright_fraction".to_owned(), value);
        }
        if let Some(value) = min_dark_fraction {
            threshold.insert("min_dark_fraction".to_owned(), value);
        }
        checks.push(RenderQualityCheckV1 {
            id: id.to_owned(),
            code: "reflection_chrome_read_missing".to_owned(),
            status: RenderQualityStatusV1::Failed,
            severity: "error".to_owned(),
            region: region.to_report(),
            observed: BTreeMap::from([
                (
                    "bright_fraction".to_owned(),
                    types::round3(chrome_read.bright_fraction),
                ),
                (
                    "dark_fraction".to_owned(),
                    types::round3(chrome_read.dark_fraction),
                ),
            ]),
            threshold,
            fix_hint: "use a studio HDR environment (environment.preset:\"studio\") and a high-tessellation sphere (segments>=256, rings>=192) so a mirror subject shows structured reflections".to_owned(),
        });
    }
    checks
}

#[derive(Debug, Clone, Copy)]
struct ReflectionChromeReadMetrics {
    bright_fraction: f32,
    dark_fraction: f32,
}

fn reflection_chrome_read_metrics(
    rgba8: &[u8],
    width: u32,
    height: u32,
    region: RenderQualityRegion,
) -> ReflectionChromeReadMetrics {
    let max_x = region.x.saturating_add(region.width).min(width);
    let max_y = region.y.saturating_add(region.height).min(height);
    let mut bright = 0usize;
    let mut dark = 0usize;
    let mut count = 0usize;
    for y in region.y..max_y {
        for x in region.x..max_x {
            let luma = pixel_luminance(rgba8, width, x, y);
            if luma >= 0.68 {
                bright += 1;
            }
            if luma <= 0.18 {
                dark += 1;
            }
            count += 1;
        }
    }
    let count = count.max(1) as f32;
    ReflectionChromeReadMetrics {
        bright_fraction: bright as f32 / count,
        dark_fraction: dark as f32 / count,
    }
}

fn reflection_chroma_range(
    rgba8: &[u8],
    width: u32,
    height: u32,
    region: RenderQualityRegion,
) -> f32 {
    let max_x = region.x.saturating_add(region.width).min(width);
    let max_y = region.y.saturating_add(region.height).min(height);
    let mut min_chroma = f32::INFINITY;
    let mut max_chroma = f32::NEG_INFINITY;
    for y in region.y..max_y {
        for x in region.x..max_x {
            let offset = ((y as usize) * (width as usize) + x as usize) * 4;
            let Some(pixel) = rgba8.get(offset..offset + 3) else {
                continue;
            };
            let r = f32::from(pixel[0]) / 255.0;
            let g = f32::from(pixel[1]) / 255.0;
            let b = f32::from(pixel[2]) / 255.0;
            let high = r.max(g).max(b);
            let low = r.min(g).min(b);
            let chroma = high - low;
            min_chroma = min_chroma.min(chroma);
            max_chroma = max_chroma.max(chroma);
        }
    }
    if min_chroma.is_finite() && max_chroma.is_finite() {
        types::round3((max_chroma - min_chroma).max(0.0))
    } else {
        0.0
    }
}

fn reflection_firefly_fraction(
    rgba8: &[u8],
    width: u32,
    height: u32,
    region: RenderQualityRegion,
) -> f32 {
    let max_x = region.x.saturating_add(region.width).min(width);
    let max_y = region.y.saturating_add(region.height).min(height);
    if max_x <= region.x || max_y <= region.y {
        return 0.0;
    }
    let mut luminance_values =
        Vec::with_capacity(region.width.saturating_mul(region.height) as usize);
    for y in region.y..max_y {
        for x in region.x..max_x {
            luminance_values.push(pixel_luminance(rgba8, width, x, y));
        }
    }
    if luminance_values.len() < 9 {
        return 0.0;
    }
    luminance_values.sort_by(f32::total_cmp);
    let p95 = percentile(&luminance_values, 0.95);
    let threshold = (p95 + 0.25).clamp(0.74, 0.97);
    let mut candidates = 0usize;
    let mut isolated = 0usize;
    for y in region.y..max_y {
        for x in region.x..max_x {
            let center = pixel_luminance(rgba8, width, x, y);
            if center < threshold {
                continue;
            }
            candidates += 1;
            if bright_neighbor_count(rgba8, width, height, x, y, center) <= 1 {
                isolated += 1;
            }
        }
    }
    if candidates == 0 {
        return 0.0;
    }
    isolated as f32 / (region.width.saturating_mul(region.height).max(1) as f32)
}

fn bright_neighbor_count(
    rgba8: &[u8],
    width: u32,
    height: u32,
    x: u32,
    y: u32,
    center_luminance: f32,
) -> usize {
    let threshold = (center_luminance - 0.12).max(0.0);
    let min_x = x.saturating_sub(1);
    let min_y = y.saturating_sub(1);
    let max_x = (x + 1).min(width.saturating_sub(1));
    let max_y = (y + 1).min(height.saturating_sub(1));
    let mut count = 0usize;
    for ny in min_y..=max_y {
        for nx in min_x..=max_x {
            if nx == x && ny == y {
                continue;
            }
            if pixel_luminance(rgba8, width, nx, ny) >= threshold {
                count += 1;
            }
        }
    }
    count
}

fn pixel_luminance(rgba8: &[u8], width: u32, x: u32, y: u32) -> f32 {
    let offset = ((y as usize) * (width as usize) + x as usize) * 4;
    let Some(pixel) = rgba8.get(offset..offset + 3) else {
        return 0.0;
    };
    (0.2126 * f32::from(pixel[0]) + 0.7152 * f32::from(pixel[1]) + 0.0722 * f32::from(pixel[2]))
        / 255.0
}

fn percentile(sorted_values: &[f32], percentile: f32) -> f32 {
    if sorted_values.is_empty() {
        return 0.0;
    }
    let index = ((sorted_values.len() - 1) as f32 * percentile.clamp(0.0, 1.0)).round() as usize;
    sorted_values[index.min(sorted_values.len() - 1)]
}