cranpose-render-wgpu 0.1.14

WGPU renderer backend for Cranpose
Documentation
use crate::effect_renderer::{
    CompositeBatchItem, CompositeSampleMode, ProjectiveSurfaceComposite, RoundedCompositeMask,
    ShaderCompositeBatchItem,
};
use crate::normalized_scene::CollectedLayer;
use crate::offscreen::OffscreenTarget;
use crate::scene::{
    BackdropLayer, DrawOp, DrawShape, EffectLayer, ImageDraw, ShadowDraw, SnapAnchor, TextDraw,
};
use crate::surface_plan::{LayerSurfaceRequirements, TranslationRenderContext};
use crate::surface_requirements::SurfaceRequirementSet;
use crate::TextSystemState;
use cranpose_core::NodeId;
use cranpose_render_common::graph::LayerNode;
use cranpose_render_common::raster_cache::LayerRasterCacheKey;
use cranpose_ui_graphics::{BlendMode, LayerShape, Rect, RenderEffect, RuntimeShader};
use std::ops::Range;
use std::rc::Rc;

pub(crate) struct LayerSurface {
    pub(crate) target: LayerSurfaceTexture,
    pub(crate) logical_rect: Rect,
    pub(crate) composite_alpha: f32,
    pub(crate) blend_mode: BlendMode,
    pub(crate) rounded_clip: Option<LayerSurfaceRoundedClip>,
    pub(crate) backdrop: Option<RenderEffect>,
    pub(crate) deferred_effect: Option<RenderEffect>,
    pub(crate) sample_mode: CompositeSampleMode,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct LayerSurfaceRoundedClip {
    pub(crate) rect: Rect,
    pub(crate) radii: [f32; 4],
}

impl LayerSurfaceRoundedClip {
    pub(crate) fn from_layer(layer: &LayerNode) -> Option<Self> {
        if !layer.graphics_layer.clip {
            return None;
        }
        let LayerShape::Rounded(shape) = layer.graphics_layer.shape else {
            return None;
        };
        let radii = shape.resolve(layer.local_bounds.width, layer.local_bounds.height);
        if radii.top_left <= f32::EPSILON
            && radii.top_right <= f32::EPSILON
            && radii.bottom_left <= f32::EPSILON
            && radii.bottom_right <= f32::EPSILON
        {
            return None;
        }
        Some(Self {
            rect: layer.local_bounds,
            radii: [
                radii.top_left,
                radii.top_right,
                radii.bottom_left,
                radii.bottom_right,
            ],
        })
    }

    pub(crate) fn composite_mask(
        self,
        surface_logical_rect: Rect,
        dest_rect: Rect,
    ) -> RoundedCompositeMask {
        let scale_x = if surface_logical_rect.width.abs() > f32::EPSILON {
            dest_rect.width / surface_logical_rect.width
        } else {
            1.0
        };
        let scale_y = if surface_logical_rect.height.abs() > f32::EPSILON {
            dest_rect.height / surface_logical_rect.height
        } else {
            1.0
        };
        let mask_x = dest_rect.x + (self.rect.x - surface_logical_rect.x) * scale_x;
        let mask_y = dest_rect.y + (self.rect.y - surface_logical_rect.y) * scale_y;
        let mask_width = self.rect.width * scale_x;
        let mask_height = self.rect.height * scale_y;
        let radius_scale = scale_x.abs().min(scale_y.abs());
        RoundedCompositeMask {
            rect: [mask_x, mask_y, mask_width, mask_height],
            radii: [
                self.radii[0] * radius_scale,
                self.radii[1] * radius_scale,
                self.radii[2] * radius_scale,
                self.radii[3] * radius_scale,
            ],
        }
    }
}

pub(crate) enum LayerSurfaceTexture {
    Owned(OffscreenTarget),
    Cached(Rc<OffscreenTarget>),
}

impl LayerSurfaceTexture {
    pub(crate) fn target(&self) -> &OffscreenTarget {
        match self {
            Self::Owned(target) => target,
            Self::Cached(target) => target.as_ref(),
        }
    }
}

pub(crate) struct CachedLayerSurface {
    pub(crate) target: Rc<OffscreenTarget>,
    pub(crate) logical_rect: Rect,
    pub(crate) byte_size: u64,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct DevicePixelBounds {
    pub(crate) x: f32,
    pub(crate) y: f32,
    pub(crate) width: u32,
    pub(crate) height: u32,
}

pub(crate) trait SurfaceExecutionBackend {
    fn max_texture_dim(&self) -> u32;
    fn acquire_retained_surface(&mut self, width: u32, height: u32) -> OffscreenTarget;
    fn acquire_frame_surface(&mut self, width: u32, height: u32) -> OffscreenTarget;
    fn release_frame_surface(&mut self, target: OffscreenTarget);
    fn release_layer_surface_target(&mut self, target: LayerSurfaceTexture);
    fn cached_layer_surface(
        &mut self,
        key: &LayerRasterCacheKey,
    ) -> Option<(Rc<OffscreenTarget>, Rect)>;
    fn admit_layer_surface_cache_miss(&mut self, key: &LayerRasterCacheKey) -> bool;
    fn insert_cached_layer_surface(
        &mut self,
        key: LayerRasterCacheKey,
        target: OffscreenTarget,
        logical_rect: Rect,
    ) -> Rc<OffscreenTarget>;
    fn layer_raster_cache_candidate(
        &mut self,
        layer: &LayerNode,
        root_scale: f32,
        has_backdrop_underlay: bool,
        allow_runtime_cache: bool,
        logical_rect_override: Option<Rect>,
    ) -> Option<(LayerRasterCacheKey, Rect)>;
    fn layer_surface_requirements(&mut self, layer: &LayerNode) -> LayerSurfaceRequirements;
    fn collect_layer_contents_with_translation_context<'a>(
        &mut self,
        text_state: &mut TextSystemState,
        layer: &'a LayerNode,
        inherited_clip: Option<Rect>,
        inherited_translated_snap_anchor: Option<SnapAnchor>,
        translation_context: TranslationRenderContext,
    ) -> CollectedLayer<'a>;
    fn clear_target_view_with_load_op(
        &mut self,
        target_view: &wgpu::TextureView,
        load_op: wgpu::LoadOp<wgpu::Color>,
    );
    #[allow(clippy::too_many_arguments)]
    fn render_non_effect_segment(
        &mut self,
        text_state: &mut TextSystemState,
        target_view: &wgpu::TextureView,
        shapes: &[DrawShape],
        images: &[ImageDraw],
        texts: &[TextDraw],
        shadow_draws: &[ShadowDraw],
        draw_ops: &[DrawOp],
        z_start: usize,
        z_end: usize,
        effect_z_ranges: &[Range<usize>],
        width: u32,
        height: u32,
        root_scale: f32,
        initial_load_op: wgpu::LoadOp<wgpu::Color>,
    ) -> Result<(), String>;
    #[allow(clippy::too_many_arguments)]
    fn render_non_effect_segment_with_composites(
        &mut self,
        text_state: &mut TextSystemState,
        target_view: &wgpu::TextureView,
        shapes: &[DrawShape],
        images: &[ImageDraw],
        texts: &[TextDraw],
        shadow_draws: &[ShadowDraw],
        draw_ops: &[DrawOp],
        z_start: usize,
        z_end: usize,
        effect_z_ranges: &[Range<usize>],
        composites: &[(usize, CompositeBatchItem<'_>)],
        shader_composites: &[(usize, ShaderCompositeBatchItem<'_>)],
        width: u32,
        height: u32,
        root_scale: f32,
        initial_load_op: wgpu::LoadOp<wgpu::Color>,
    ) -> Result<(), String>;
    #[allow(clippy::too_many_arguments)]
    fn render_range_with_layer_events_to_target(
        &mut self,
        text_state: &mut TextSystemState,
        target: &OffscreenTarget,
        shapes: &[DrawShape],
        images: &[ImageDraw],
        texts: &[TextDraw],
        shadow_draws: &[ShadowDraw],
        draw_ops: &[DrawOp],
        effect_layers: &[EffectLayer],
        backdrop_layers: &[BackdropLayer],
        z_start: usize,
        z_end: usize,
        excluded_effect_layer: Option<usize>,
        width: u32,
        height: u32,
        root_scale: f32,
        backdrop_underlay: Option<&OffscreenTarget>,
        initial_load_op: wgpu::LoadOp<wgpu::Color>,
    ) -> Result<(), String>;
    fn render_shadow_draw(
        &mut self,
        text_state: &mut TextSystemState,
        target_view: &wgpu::TextureView,
        shadow: &ShadowDraw,
        width: u32,
        height: u32,
        root_scale: f32,
    );
    #[allow(clippy::too_many_arguments)]
    fn composite_to_view_projective(
        &mut self,
        source: &OffscreenTarget,
        dest_view: &wgpu::TextureView,
        viewport: (u32, u32),
        source_size: (f32, f32),
        inverse_matrix: [[f32; 3]; 3],
        dest_bounds: [[f32; 2]; 4],
        alpha: f32,
        load_op: wgpu::LoadOp<wgpu::Color>,
        scissor: Option<(u32, u32, u32, u32)>,
        blend_mode: BlendMode,
        sample_mode: CompositeSampleMode,
    );
    fn composite_projective_surfaces_to_view(
        &mut self,
        dest_view: &wgpu::TextureView,
        viewport: (u32, u32),
        composites: &[ProjectiveSurfaceComposite<'_>],
    );
    fn composite_surface_batch_to_view(
        &mut self,
        dest_view: &wgpu::TextureView,
        viewport: (u32, u32),
        load_op: wgpu::LoadOp<wgpu::Color>,
        composites: &[CompositeBatchItem<'_>],
    );
    fn copy_texture_region_to_target(
        &mut self,
        source: &OffscreenTarget,
        source_origin: (u32, u32),
        target: &OffscreenTarget,
        size: (u32, u32),
    ) -> bool;
    fn shader_composite_batch_to_view(
        &mut self,
        dest_view: &wgpu::TextureView,
        viewport: (u32, u32),
        load_op: wgpu::LoadOp<wgpu::Color>,
        composites: &[ShaderCompositeBatchItem<'_>],
    ) -> bool;
    #[allow(clippy::too_many_arguments)]
    fn composite_to_view_scissored_with_alpha_and_mask_and_blend_mode(
        &mut self,
        source: &OffscreenTarget,
        dest_view: &wgpu::TextureView,
        alpha: f32,
        load_op: wgpu::LoadOp<wgpu::Color>,
        scissor: Option<(u32, u32, u32, u32)>,
        rounded_mask: Option<RoundedCompositeMask>,
        blend_mode: BlendMode,
        dest_viewport: Option<(f32, f32, f32, f32)>,
        sample_mode: CompositeSampleMode,
    );
    #[allow(clippy::too_many_arguments)]
    fn apply_effect_and_composite_to_view(
        &mut self,
        source: &OffscreenTarget,
        effect: &RenderEffect,
        effect_rect: [f32; 4],
        dest_view: &wgpu::TextureView,
        alpha: f32,
        load_op: wgpu::LoadOp<wgpu::Color>,
        scissor: Option<(u32, u32, u32, u32)>,
        blend_mode: BlendMode,
        dest_viewport: Option<(f32, f32, f32, f32)>,
        sample_mode: CompositeSampleMode,
    ) -> Result<(), String>;
    #[allow(clippy::too_many_arguments)]
    fn apply_shader_and_composite_to_view(
        &mut self,
        source: &OffscreenTarget,
        shader: &RuntimeShader,
        effect_rect: [f32; 4],
        dest_view: &wgpu::TextureView,
        alpha: f32,
        load_op: wgpu::LoadOp<wgpu::Color>,
        scissor: Option<(u32, u32, u32, u32)>,
        blend_mode: BlendMode,
        dest_viewport: Option<(f32, f32, f32, f32)>,
        sample_mode: CompositeSampleMode,
    );
    #[allow(clippy::too_many_arguments)]
    fn apply_shader_and_composite_to_view_projective(
        &mut self,
        source: &OffscreenTarget,
        shader: &RuntimeShader,
        effect_rect: [f32; 4],
        dest_view: &wgpu::TextureView,
        viewport: (u32, u32),
        source_size: (f32, f32),
        inverse_matrix: [[f32; 3]; 3],
        dest_bounds: [[f32; 2]; 4],
        alpha: f32,
        load_op: wgpu::LoadOp<wgpu::Color>,
        scissor: Option<(u32, u32, u32, u32)>,
        blend_mode: BlendMode,
        sample_mode: CompositeSampleMode,
    );
    #[allow(clippy::too_many_arguments)]
    fn apply_effect_and_composite_to_view_projective(
        &mut self,
        source: &OffscreenTarget,
        effect: &RenderEffect,
        effect_rect: [f32; 4],
        dest_view: &wgpu::TextureView,
        viewport: (u32, u32),
        source_size: (f32, f32),
        inverse_matrix: [[f32; 3]; 3],
        dest_bounds: [[f32; 2]; 4],
        alpha: f32,
        load_op: wgpu::LoadOp<wgpu::Color>,
        scissor: Option<(u32, u32, u32, u32)>,
        blend_mode: BlendMode,
        sample_mode: CompositeSampleMode,
    ) -> Result<(), String>;
    fn is_render_effect_supported(&self, effect: &RenderEffect) -> bool;
    fn warn_unsupported_effect_once(&self);
    fn record_layer_cache_miss(&self, width: u32, height: u32);
    fn record_isolated_layer_render(
        &self,
        width: u32,
        height: u32,
        node_id: Option<NodeId>,
        logical_rect: Rect,
        requirements: SurfaceRequirementSet,
    );
}

#[cfg(test)]
mod tests {
    use super::LayerSurfaceRoundedClip;
    use cranpose_ui_graphics::Rect;

    #[test]
    fn rounded_clip_mask_maps_surface_subrect_to_destination_pixels() {
        let clip = LayerSurfaceRoundedClip {
            rect: Rect {
                x: 10.0,
                y: 20.0,
                width: 100.0,
                height: 40.0,
            },
            radii: [4.0, 8.0, 12.0, 16.0],
        };

        let mask = clip.composite_mask(
            Rect {
                x: 0.0,
                y: 10.0,
                width: 200.0,
                height: 100.0,
            },
            Rect {
                x: 40.0,
                y: 80.0,
                width: 400.0,
                height: 200.0,
            },
        );

        assert_eq!(mask.rect, [60.0, 100.0, 200.0, 80.0]);
        assert_eq!(mask.radii, [8.0, 16.0, 24.0, 32.0]);
    }
}