Skip to main content

vello_common/
viewport.rs

1// Copyright 2026 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Shared viewport state.
5
6use crate::clip::{ClipState, PathDataRef};
7use crate::filter::FilterData;
8use crate::kurbo::{Affine, BezPath};
9use crate::strip_generator::StripGenerator;
10use alloc::vec::Vec;
11use fearless_simd::Level;
12use peniko::Fill;
13
14/// Viewport state storing information about clip state and active strip generators in
15/// currently active root viewports.
16#[derive(Debug)]
17pub struct ViewportState {
18    clip_state: ClipState,
19    strip_generator: StripGenerator,
20    strip_generator_stack: Vec<StripGenerator>,
21    level: Level,
22}
23
24impl ViewportState {
25    /// Create a new viewport state.
26    pub fn new(width: u16, height: u16, level: Level) -> Self {
27        Self {
28            clip_state: ClipState::new(),
29            strip_generator: StripGenerator::new(width, height, level),
30            strip_generator_stack: Vec::new(),
31            level,
32        }
33    }
34
35    /// Width of the active viewport.
36    pub fn width(&self) -> u16 {
37        self.strip_generator.width()
38    }
39
40    /// Height of the active viewport.
41    pub fn height(&self) -> u16 {
42        self.strip_generator.height()
43    }
44
45    /// Return the current clip path.
46    pub fn clip(&self) -> Option<PathDataRef<'_>> {
47        self.clip_state.get()
48    }
49
50    /// Whether any root viewports are currently pushed.
51    pub fn has_root_viewports(&self) -> bool {
52        !self.strip_generator_stack.is_empty()
53    }
54
55    /// Use the active strip generator together with the current clip.
56    pub fn with_generator_and_clip<R>(
57        &mut self,
58        f: impl FnOnce(&mut StripGenerator, Option<PathDataRef<'_>>) -> R,
59    ) -> R {
60        let clip = self.clip_state.get();
61
62        f(&mut self.strip_generator, clip)
63    }
64
65    /// Push a new clip path.
66    pub fn push_clip(
67        &mut self,
68        path: &BezPath,
69        fill_rule: Fill,
70        transform: Affine,
71        aliasing_threshold: Option<u8>,
72    ) {
73        self.clip_state.push_clip(
74            path,
75            &mut self.strip_generator,
76            fill_rule,
77            transform,
78            aliasing_threshold,
79        );
80    }
81
82    /// Pop the last clip path.
83    pub fn pop_clip(&mut self) {
84        self.clip_state.pop_clip();
85    }
86
87    /// Push a new root viewport.
88    pub fn push_root_viewport(&mut self, filter_data: &FilterData) {
89        let padding = filter_data.source_padding;
90        let width = self
91            .strip_generator
92            .width()
93            .saturating_add(padding.left)
94            .saturating_add(padding.right);
95        let height = self
96            .strip_generator
97            .height()
98            .saturating_add(padding.top)
99            .saturating_add(padding.bottom);
100        // TODO: Use a pool of strip generators.
101        let filter_generator = StripGenerator::new(width, height, self.level);
102        let parent_generator = core::mem::replace(&mut self.strip_generator, filter_generator);
103        self.strip_generator_stack.push(parent_generator);
104
105        self.clip_state
106            .push_root_viewport(filter_data.source_shift(), &mut self.strip_generator);
107    }
108
109    /// Pop the last root viewport.
110    pub fn pop_root_viewport(&mut self) {
111        self.strip_generator = self
112            .strip_generator_stack
113            .pop()
114            .expect("root viewport stack underflow");
115
116        self.clip_state.pop_root_viewport(&mut self.strip_generator);
117    }
118
119    /// Reset strip generation and clipping for a new viewport.
120    pub fn reset(&mut self, width: u16, height: u16) {
121        self.clip_state.reset();
122        self.strip_generator_stack.clear();
123        self.strip_generator.reset(width, height);
124    }
125}