opendefocus 0.1.10

An advanced open-source convolution library for image post-processing
Documentation
use crate::datamodel::settings_to_convolve_settings;
use crate::renders::resize::MipmapBuffer;
use crate::traits::TraitBounds;
use glam::{UVec2};
use ndarray::{Array3, ArrayView3};
use opendefocus_datastructure::{
    Settings,
    render::{FilterMode, RenderSpecs},
};
use opendefocus_shared::{ConvolveSettings, WORKGROUP_SIZE};

use crate::error::Result;

pub const OUTPUT_CHANNELS: usize = 5;

pub struct Backend {
    pub device_name: String,
    pub backend: String,
}

pub trait ConvolveRunner {
    fn backend(&self) -> Backend;

    async fn execute_kernel_pass(
        &self,
        output_image: &mut [f32],
        input_image: Array3<f32>,
        inpaint: Array3<f32>,
        filters: &MipmapBuffer<f32>,
        depth: Array3<f32>,
        convolve_settings: ConvolveSettings,
        cached_samples: &[f32],
    ) -> Result<()>;

    fn prepare_data<T: TraitBounds>(&self, data: &[T]) -> (Vec<f32>, usize) {
        let gpu_data: Vec<f32> = data
            .iter()
            .map(|&x| x.to_f32_normalized().unwrap_or_default())
            .collect();
        (gpu_data, data.len())
    }

    fn finalize_data<T: TraitBounds>(&self, gpu_data: &[f32], output: &mut [T]) {
        for (i, &val) in gpu_data.iter().take(output.len()).enumerate() {
            output[i] = T::from_f32_normalized(val).unwrap_or_default();
        }
    }

    async fn convolve<'image, T: TraitBounds>(
        &self,
        output_image: &mut [T],
        input_image: ArrayView3<'image, T>,
        inpaint: Array3<T>,
        filters: &MipmapBuffer<f32>,
        depth: Array3<f32>,
        render_specs: &RenderSpecs,
        settings: &Settings,
    ) -> Result<()> {
        if output_image.len() <= 1 {
            return Ok(());
        }

        let (mut gpu_output_image_data, original_output_image_size) =
            self.prepare_data(output_image);

        let image_elements = output_image.len() / OUTPUT_CHANNELS;

        let convolve_settings = settings_to_convolve_settings(
            settings,
            render_specs,
            if settings.render.filter.mode() == FilterMode::Simple {
                let resolution = settings.render.filter.calculate_filter_box(settings.bokeh.aspect_ratio);
                UVec2::new(resolution[2] - resolution[0], resolution[3] - resolution[1])
            } else {
                filters.get_resolution().as_uvec2()
            },
            image_elements as u32,
        );
        let cached_samples = convolve_settings.get_sample_weights();

        let input_image_normalized =
            input_image.mapv(|f| f.to_f32_normalized().unwrap_or_default());
        let inpaint_normalized = inpaint.mapv(|f| f.to_f32_normalized().unwrap_or_default());

        self.execute_kernel_pass(
            &mut gpu_output_image_data,
            input_image_normalized,
            inpaint_normalized,
            filters,
            depth,
            convolve_settings,
            &cached_samples,
        )
        .await?;

        gpu_output_image_data.truncate(original_output_image_size);

        self.finalize_data::<T>(&gpu_output_image_data, output_image);

        Ok(())
    }
    fn compute_workgroup_count(resolution: UVec2) -> UVec2 {
        UVec2::new(
            resolution.x.div_ceil(WORKGROUP_SIZE),
            resolution.y.div_ceil(WORKGROUP_SIZE),
        )
    }
}