dream-ini 0.2.0

Import Morrowind.ini settings into OpenMW configuration files
Documentation
// SPDX-License-Identifier: GPL-3.0-only

use super::texture::TextureImage;
use crate::gui::portmaster::raster::usize_to_f32;

const TEXTURE_WIDTH: usize = 64;
const TEXTURE_HEIGHT: usize = 4;
const VERTEX_COLOR: [u8; 4] = [192, 208, 224, 255];
const BACKGROUND_COLOR: [u8; 4] = [17, 20, 28, 255];
const LT4_RECTS: usize = 20;
const MID_RECTS: usize = 152;
const WIDE_RECTS: usize = 20;
const LT4_WIDTH: usize = 2;
const MID_WIDTH: usize = 5;
const WIDE_WIDTH: usize = 10;
const RECT_GAP: usize = 1;

#[derive(Debug, PartialEq, Eq)]
pub(super) struct SampledRectModulatedWorkload {
    viewport_width: usize,
    viewport_height: usize,
    mesh: egui::Mesh,
    texture: TextureImage,
    stats: SampledRectModulatedWorkloadStats,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct SampledRectModulatedWorkloadStats {
    pub(super) rects: usize,
    pub(super) pixels: usize,
    pub(super) lt4_pixels: usize,
    pub(super) mid_pixels: usize,
    pub(super) wide_pixels: usize,
    pub(super) texture_transparent_lanes: usize,
    pub(super) texture_translucent_lanes: usize,
    pub(super) texture_opaque_lanes: usize,
}

impl SampledRectModulatedWorkload {
    pub(super) const DESCRIPTION: &'static str = "deterministic sampled separable textured rects with uniform non-white vertex modulation; fixed span mix target lt4=4% 4..7=76% 8..15=20%; 64x4 contiguous mixed-alpha texture";

    pub(super) fn new(viewport_width: usize, viewport_height: usize) -> Self {
        let texture = benchmark_texture();
        let (mesh, stats) = benchmark_mesh(viewport_width, viewport_height, &texture);
        Self {
            viewport_width,
            viewport_height,
            mesh,
            texture,
            stats,
        }
    }

    pub(super) const fn matches_viewport(&self, width: usize, height: usize) -> bool {
        self.viewport_width == width && self.viewport_height == height
    }

    pub(super) fn mesh(&self) -> &egui::Mesh {
        &self.mesh
    }

    pub(super) fn texture(&self) -> &TextureImage {
        &self.texture
    }

    pub(super) const fn stats(&self) -> SampledRectModulatedWorkloadStats {
        self.stats
    }

    pub(super) fn config_log_line(&self, frame_limit: u64) -> String {
        format!(
            "portmaster render benchmark config name=sampled-rect-modulated frames={} workload={:?} viewport={}x{} rects={} pixels={} span_px_lt4={} span_px_4_7={} span_px_8_15={} target_distribution=lt4:4%,4..7:76%,8..15:20%,16_plus:0% vertex_color_rgba={:?} texture={}x{} texture_pattern=deterministic-contiguous-mixed-alpha texture_alpha_lanes transparent={} translucent={} opaque={} fixed_workload_per_frame=true",
            frame_limit,
            Self::DESCRIPTION,
            self.viewport_width,
            self.viewport_height,
            self.stats.rects,
            self.stats.pixels,
            self.stats.lt4_pixels,
            self.stats.mid_pixels,
            self.stats.wide_pixels,
            VERTEX_COLOR,
            self.texture.width,
            self.texture.height,
            self.stats.texture_transparent_lanes,
            self.stats.texture_translucent_lanes,
            self.stats.texture_opaque_lanes,
        )
    }
}

fn benchmark_mesh(
    viewport_width: usize,
    viewport_height: usize,
    texture: &TextureImage,
) -> (egui::Mesh, SampledRectModulatedWorkloadStats) {
    let rects = LT4_RECTS + MID_RECTS + WIDE_RECTS;
    let mut mesh = egui::Mesh {
        indices: Vec::with_capacity(rects * 6),
        vertices: Vec::with_capacity(rects * 4),
        texture_id: egui::TextureId::Managed(0),
    };
    let mut cursor = MeshCursor::new(viewport_width, viewport_height);
    let mut stats = SampledRectModulatedWorkloadStats {
        rects,
        pixels: 0,
        lt4_pixels: 0,
        mid_pixels: 0,
        wide_pixels: 0,
        texture_transparent_lanes: 0,
        texture_translucent_lanes: 0,
        texture_opaque_lanes: 0,
    };

    for _ in 0..LT4_RECTS {
        push_rect(&mut mesh, &mut cursor, LT4_WIDTH, 0, texture.width);
        stats.lt4_pixels += LT4_WIDTH;
    }
    for index in 0..MID_RECTS {
        push_rect(&mut mesh, &mut cursor, MID_WIDTH, index, texture.width);
        stats.mid_pixels += MID_WIDTH;
    }
    for index in 0..WIDE_RECTS {
        push_rect(&mut mesh, &mut cursor, WIDE_WIDTH, index, texture.width);
        stats.wide_pixels += WIDE_WIDTH;
    }
    stats.pixels = stats.lt4_pixels + stats.mid_pixels + stats.wide_pixels;
    let (transparent, translucent, opaque) = texture_alpha_lane_counts(texture);
    stats.texture_transparent_lanes = transparent;
    stats.texture_translucent_lanes = translucent;
    stats.texture_opaque_lanes = opaque;
    (mesh, stats)
}

struct MeshCursor {
    x: usize,
    y: usize,
    width: usize,
    height: usize,
}

impl MeshCursor {
    const fn new(width: usize, height: usize) -> Self {
        Self {
            x: 0,
            y: 0,
            width,
            height,
        }
    }

    fn place(&mut self, rect_width: usize) -> (usize, usize) {
        let row_width = self.width.max(rect_width);
        if self.x + rect_width > row_width {
            self.x = 0;
            self.y = self.y.saturating_add(1);
        }
        if self.y >= self.height.max(1) {
            self.y = 0;
        }
        let position = (self.x, self.y);
        self.x = self.x.saturating_add(rect_width + RECT_GAP);
        position
    }
}

fn push_rect(
    mesh: &mut egui::Mesh,
    cursor: &mut MeshCursor,
    rect_width: usize,
    index: usize,
    texture_width: usize,
) {
    let (x, y) = cursor.place(rect_width);
    let base = u32::try_from(mesh.vertices.len()).expect("benchmark mesh vertex count fits u32");
    let max_texel = usize_to_f32(texture_width - 1);
    let source_x = index % (texture_width - rect_width);
    let uv_left = (usize_to_f32(source_x) - 0.5) / max_texel;
    let uv_right = usize_to_f32(source_x + rect_width) - 0.5;
    let uv_right = uv_right / max_texel;
    let texel_y = (index / texture_width) % TEXTURE_HEIGHT;
    let uv_y = usize_to_f32(texel_y) / usize_to_f32(TEXTURE_HEIGHT - 1);
    let min = egui::pos2(usize_to_f32(x), usize_to_f32(y));
    let max = egui::pos2(usize_to_f32(x + rect_width), usize_to_f32(y + 1));
    mesh.vertices.extend_from_slice(&[
        vertex(min.x, min.y, uv_left, uv_y),
        vertex(max.x, min.y, uv_right, uv_y),
        vertex(min.x, max.y, uv_left, uv_y),
        vertex(max.x, max.y, uv_right, uv_y),
    ]);
    mesh.indices
        .extend_from_slice(&[base, base + 1, base + 2, base + 1, base + 3, base + 2]);
}

fn vertex(x: f32, y: f32, uv_x: f32, uv_y: f32) -> egui::epaint::Vertex {
    egui::epaint::Vertex {
        pos: egui::pos2(x, y),
        uv: egui::pos2(uv_x, uv_y),
        color: egui::Color32::from_rgba_premultiplied(
            VERTEX_COLOR[0],
            VERTEX_COLOR[1],
            VERTEX_COLOR[2],
            VERTEX_COLOR[3],
        ),
    }
}

fn benchmark_texture() -> TextureImage {
    let mut pixels = Vec::with_capacity(TEXTURE_WIDTH * TEXTURE_HEIGHT * 4);
    for y in 0..TEXTURE_HEIGHT {
        for x in 0..TEXTURE_WIDTH {
            let alpha = match (x + y * 3) % 8 {
                0 | 5 => 0,
                2 | 6 => 255,
                1 => 96,
                3 => 160,
                4 => 64,
                _ => 208,
            };
            let red = 32 + u8::try_from((x * 37 + y * 19) % 192).expect("red fits u8");
            let green = 48 + u8::try_from((x * 17 + y * 43) % 176).expect("green fits u8");
            let blue = 64 + u8::try_from((x * 29 + y * 11) % 160).expect("blue fits u8");
            pixels.extend_from_slice(&[red, green, blue, alpha]);
        }
    }
    TextureImage {
        width: TEXTURE_WIDTH,
        height: TEXTURE_HEIGHT,
        pixels,
    }
}

fn texture_alpha_lane_counts(texture: &TextureImage) -> (usize, usize, usize) {
    let mut transparent = 0;
    let mut translucent = 0;
    let mut opaque = 0;
    for pixel in texture.pixels.chunks_exact(4) {
        match pixel[3] {
            0 => transparent += 1,
            255 => opaque += 1,
            _ => translucent += 1,
        }
    }
    (transparent, translucent, opaque)
}

pub(super) const fn background_color() -> [u8; 4] {
    BACKGROUND_COLOR
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn sampled_rect_modulated_workload_has_deterministic_span_buckets() {
        let left = SampledRectModulatedWorkload::new(640, 480);
        let right = SampledRectModulatedWorkload::new(640, 480);

        assert_eq!(left.stats(), right.stats());
        assert_eq!(left.mesh.vertices, right.mesh.vertices);
        assert_eq!(left.mesh.indices, right.mesh.indices);
        assert_eq!(left.texture.pixels, right.texture.pixels);
        assert_eq!(left.stats.rects, 192);
        assert_eq!(left.stats.pixels, 1_000);
        assert_eq!(left.stats.lt4_pixels, 40);
        assert_eq!(left.stats.mid_pixels, 760);
        assert_eq!(left.stats.wide_pixels, 200);
    }

    #[test]
    fn sampled_rect_modulated_texture_is_mixed_alpha_dominant() {
        let workload = SampledRectModulatedWorkload::new(640, 480);
        let stats = workload.stats();

        assert!(stats.texture_translucent_lanes > stats.texture_transparent_lanes);
        assert!(stats.texture_translucent_lanes > stats.texture_opaque_lanes);
        assert!(stats.texture_transparent_lanes > 0);
        assert!(stats.texture_opaque_lanes > 0);
    }
}