maplibre/sdf/
upload_system.rs

1//! Uploads data to the GPU which is needed for rendering.
2
3use std::iter;
4
5use crate::{
6    context::MapContext,
7    coords::ViewRegion,
8    render::{
9        eventually::{Eventually, Eventually::Initialized},
10        shaders::{SDFShaderFeatureMetadata, ShaderLayerMetadata},
11        tile_view_pattern::DEFAULT_TILE_SIZE,
12        view_state::ViewStatePadding,
13        Renderer,
14    },
15    sdf::{SymbolBufferPool, SymbolLayerData, SymbolLayersDataComponent},
16    style::{layer::LayerPaint, Style},
17    tcs::{
18        system::{SystemError, SystemResult},
19        tiles::Tiles,
20    },
21};
22
23pub fn upload_system(
24    MapContext {
25        world,
26        style,
27        view_state,
28        renderer: Renderer { queue, .. },
29        ..
30    }: &mut MapContext,
31) -> SystemResult {
32    let Some(Initialized(symbol_buffer_pool)) = world
33        .resources
34        .query_mut::<&mut Eventually<SymbolBufferPool>>()
35    else {
36        return Err(SystemError::Dependencies);
37    };
38
39    let view_region = view_state.create_view_region(
40        view_state.zoom().zoom_level(DEFAULT_TILE_SIZE),
41        ViewStatePadding::Loose,
42    );
43
44    let zoom = view_state.zoom().level();
45
46    if let Some(view_region) = &view_region {
47        upload_symbol_layer(
48            symbol_buffer_pool,
49            queue,
50            &mut world.tiles,
51            style,
52            view_region,
53            zoom,
54        );
55    }
56
57    Ok(())
58}
59
60// TODO cleanup, duplicated
61fn upload_symbol_layer(
62    symbol_buffer_pool: &mut SymbolBufferPool,
63    queue: &wgpu::Queue,
64    tiles: &mut Tiles,
65    style: &Style,
66    view_region: &ViewRegion,
67    zoom: f32,
68) {
69    // Upload all tessellated layers which are in view
70    for coords in view_region.iter() {
71        let Some(vector_layers) = tiles.query_mut::<&SymbolLayersDataComponent>(coords) else {
72            continue;
73        };
74
75        let loaded_layers = symbol_buffer_pool
76            .get_loaded_style_layers_at(coords)
77            .unwrap_or_default();
78
79        let available_layers = vector_layers
80            .layers
81            .iter()
82            .filter(|data| !loaded_layers.contains(data.source_layer.as_str()))
83            .collect::<Vec<_>>();
84
85        for style_layer in &style.layers {
86            let layer_id = &style_layer.id;
87            let source_layer = match style_layer.source_layer.as_ref() {
88                Some(layer) => layer,
89                None => {
90                    log::trace!("style layer {layer_id} does not have a source layer");
91                    continue;
92                }
93            };
94
95            let Some(SymbolLayerData {
96                coords,
97                features,
98                //buffer,
99                new_buffer: buffer,
100                ..
101            }) = available_layers
102                .iter()
103                .find(|layer| source_layer.as_str() == layer.source_layer)
104            else {
105                continue;
106            };
107
108            // Per-vertex opacity metadata. Default to 1.0 (visible) so text renders
109            // even when collision detection features are not yet populated.
110            let metadata_count = if features.is_empty() {
111                buffer.buffer.vertices.len()
112            } else {
113                features
114                    .last()
115                    .map(|feature| feature.indices.end)
116                    .unwrap_or_default()
117            };
118            let feature_metadata = iter::repeat(SDFShaderFeatureMetadata { opacity: 1.0 })
119                .take(metadata_count)
120                .collect::<Vec<_>>();
121
122            // FIXME avoid uploading empty indices
123            if buffer.buffer.indices.is_empty() {
124                continue;
125            }
126
127            // Extract text-size from style (default 16.0 per MapLibre GL JS spec)
128            let text_size = match &style_layer.paint {
129                Some(LayerPaint::Symbol(paint)) => paint
130                    .text_size
131                    .as_ref()
132                    .map(|s| s.evaluate_at_zoom(zoom))
133                    .unwrap_or(16.0),
134                _ => 16.0,
135            };
136
137            log::debug!("Allocating geometry at {coords}");
138            symbol_buffer_pool.allocate_layer_geometry(
139                queue,
140                *coords,
141                style_layer.clone(),
142                buffer,
143                ShaderLayerMetadata {
144                    z_index: style_layer.index as f32,
145                    line_width: text_size, // repurposed as text_size for SDF pipeline
146                },
147                &feature_metadata,
148            );
149        }
150    }
151}