maplibre/render/tile_view_pattern/
pattern.rs

1use std::{collections::HashSet, marker::PhantomData};
2
3use crate::{
4    coords::{ViewRegion, Zoom},
5    render::{
6        camera::ViewProjection,
7        resource::{BackingBufferDescriptor, Queue},
8        shaders::ShaderTileMetadata,
9        tile_view_pattern::{HasTile, SourceShapes, TileShape, ViewTile},
10    },
11    tcs::world::World,
12};
13
14// FIXME: If network is very slow, this pattern size can
15// increase dramatically.
16// E.g. imagine if a pattern for zoom level 18 is drawn
17// when completely zoomed out.
18pub const DEFAULT_TILE_VIEW_PATTERN_SIZE: wgpu::BufferAddress = 512;
19pub const CHILDREN_SEARCH_DEPTH: usize = 4;
20
21#[derive(Debug)]
22struct BackingBuffer<B> {
23    /// The internal structure which is used for storage
24    inner: B,
25    /// The size of the `inner` buffer
26    inner_size: wgpu::BufferAddress,
27}
28
29impl<B> BackingBuffer<B> {
30    fn new(inner: B, inner_size: wgpu::BufferAddress) -> Self {
31        Self { inner, inner_size }
32    }
33}
34
35/// The tile mask pattern assigns each tile a value which can be used for stencil testing.
36pub struct TileViewPattern<Q, B> {
37    view_tiles: Vec<ViewTile>,
38    view_tiles_buffer: BackingBuffer<B>,
39    phantom_q: PhantomData<Q>,
40}
41
42impl<Q: Queue<B>, B> TileViewPattern<Q, B> {
43    pub fn new(view_tiles_buffer: BackingBufferDescriptor<B>) -> Self {
44        Self {
45            view_tiles: Vec::with_capacity(64),
46            view_tiles_buffer: BackingBuffer::new(
47                view_tiles_buffer.buffer,
48                view_tiles_buffer.inner_size,
49            ),
50            phantom_q: Default::default(),
51        }
52    }
53
54    #[tracing::instrument(skip_all)]
55    #[must_use]
56    pub fn generate_pattern<T: HasTile>(
57        &self,
58        view_region: &ViewRegion,
59        container: &T,
60        zoom: Zoom,
61        world: &World,
62    ) -> Vec<ViewTile> {
63        let mut view_tiles = Vec::with_capacity(self.view_tiles.len());
64        let mut source_tiles = HashSet::new(); // TODO: Optimization potential: Replace wit a bitmap, that allows false-negative matches
65
66        for coords in view_region.iter() {
67            if coords.build_quad_key().is_none() {
68                continue;
69            }
70
71            let source_shapes = {
72                if container.has_tile(coords, world) {
73                    SourceShapes::SourceEqTarget(TileShape::new(coords, zoom))
74                } else if let Some(parent_coords) = container.get_available_parent(coords, world) {
75                    log::debug!("Could not find data at {coords}. Falling back to {parent_coords}");
76
77                    if source_tiles.contains(&parent_coords) {
78                        // Performance optimization: Suppose the map only offers zoom levels 0-14.
79                        // If we build the pattern for z=18, we won't find tiles. Thus we start
80                        // looking for parents. We might find multiple times the same parent from
81                        // tiles on z=18.
82                        continue;
83                    }
84
85                    source_tiles.insert(parent_coords);
86
87                    SourceShapes::Parent(TileShape::new(parent_coords, zoom))
88                } else if let Some(children_coords) =
89                    container.get_available_children(coords, world, CHILDREN_SEARCH_DEPTH)
90                {
91                    log::debug!(
92                        "Could not find data at {coords}. Falling back children: {children_coords:?}"
93                    );
94
95                    SourceShapes::Children(
96                        children_coords
97                            .iter()
98                            .map(|child_coord| TileShape::new(*child_coord, zoom))
99                            .collect(),
100                    )
101                } else {
102                    SourceShapes::None
103                }
104            };
105
106            view_tiles.push(ViewTile {
107                target: coords,
108                source: source_shapes,
109            });
110        }
111
112        view_tiles
113    }
114
115    pub fn update_pattern(&mut self, mut view_tiles: Vec<ViewTile>) {
116        self.view_tiles.clear();
117        self.view_tiles.append(&mut view_tiles)
118    }
119
120    pub fn iter(&self) -> impl Iterator<Item = &ViewTile> + '_ {
121        self.view_tiles.iter()
122    }
123
124    pub fn buffer(&self) -> &B {
125        &self.view_tiles_buffer.inner
126    }
127
128    #[tracing::instrument(skip_all)]
129    pub fn upload_pattern(
130        &mut self,
131        queue: &Q,
132        view_proj: &ViewProjection,
133        viewport_width: f32,
134        viewport_height: f32,
135    ) {
136        let mut buffer = Vec::with_capacity(self.view_tiles.len());
137
138        let mut add_to_buffer = |shape: &mut TileShape| {
139            shape.set_buffer_range(buffer.len() as u64);
140            // TODO: Name `ShaderTileMetadata` is unfortunate here, because for raster rendering it actually is a layer
141            let transform = view_proj
142                .to_model_view_projection(shape.transform)
143                .downcast()
144                .into(); // TODO: move this calculation to update() fn above
145            buffer.push(ShaderTileMetadata {
146                // We are casting here from 64bit to 32bit, because 32bit is more performant and is
147                // better supported.
148                transform,
149                zoom_factor: shape.zoom_factor as f32,
150                viewport_width,
151                viewport_height,
152            });
153        };
154
155        for view_tile in &mut self.view_tiles {
156            match &mut view_tile.source {
157                SourceShapes::Parent(source_shape) => {
158                    add_to_buffer(source_shape);
159                }
160                SourceShapes::Children(source_shapes) => {
161                    for source_shape in source_shapes {
162                        add_to_buffer(source_shape);
163                    }
164                }
165                SourceShapes::SourceEqTarget(source_shape) => add_to_buffer(source_shape),
166                SourceShapes::None => {}
167            }
168        }
169
170        let raw_buffer = bytemuck::cast_slice(buffer.as_slice());
171        if raw_buffer.len() as wgpu::BufferAddress > self.view_tiles_buffer.inner_size {
172            /* TODO: We need to avoid this case by either choosing a proper size
173            TODO: (DEFAULT_TILE_VIEW_PATTERN_SIZE), or resizing the buffer */
174            panic!("Buffer is too small to store the tile pattern!");
175        }
176        queue.write_buffer(&self.view_tiles_buffer.inner, 0, raw_buffer);
177    }
178}