maplibre/render/
mod.rs

1//! This module implements the rendering algorithm of maplibre-rs. It manages the whole
2//! communication with the GPU.
3//!
4//! The render in this module is largely based on the
5//! [bevy_render](https://github.com/bevyengine/bevy/tree/aced6a/crates/bevy_render)
6//! crate with commit `aced6a`.
7//! It is dual-licensed under MIT and Apache:
8//!
9//! ```text
10//! Bevy is dual-licensed under either
11//!
12//! * MIT License (docs/LICENSE-MIT or http://opensource.org/licenses/MIT)
13//! * Apache License, Version 2.0 (docs/LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
14//!
15//! at your option.
16//! ```
17//!
18//! We appreciate the design and implementation work which as gone into it.
19//!
20
21use std::{ops::Deref, rc::Rc, sync::Arc};
22
23use crate::{
24    environment::Environment,
25    kernel::Kernel,
26    plugin::Plugin,
27    render::{
28        error::RenderError,
29        eventually::Eventually,
30        graph::{EmptyNode, RenderGraph},
31        main_pass::{MainPassDriverNode, MainPassNode},
32        resource::{Head, Surface, Texture, TextureView},
33        settings::{RendererSettings, WgpuSettings},
34        systems::{
35            cleanup_system::cleanup_system, resource_system::ResourceSystem,
36            sort_phase_system::sort_phase_system,
37            tile_view_pattern_system::tile_view_pattern_system,
38        },
39    },
40    schedule::{Schedule, StageLabel},
41    tcs::{
42        system::{stage::SystemStage, SystemContainer},
43        world::World,
44    },
45    window::{HeadedMapWindow, MapWindow},
46};
47
48pub mod graph;
49pub mod resource;
50mod systems;
51
52// Rendering internals
53mod graph_runner;
54mod main_pass;
55pub mod shaders;
56mod translucent_pass; // TODO: Make private
57
58// Public API
59pub mod builder;
60pub mod camera;
61pub mod error;
62pub mod eventually;
63pub mod render_commands;
64pub mod render_phase;
65pub mod settings;
66pub mod tile_view_pattern;
67pub mod view_state;
68
69pub use shaders::ShaderVertex;
70
71use crate::{
72    render::{
73        render_phase::{LayerItem, RenderPhase, TileMaskItem, TranslucentItem},
74        systems::{graph_runner_system::GraphRunnerSystem, upload_system::upload_system},
75        tile_view_pattern::{ViewTileSources, WgpuTileViewPattern},
76        translucent_pass::TranslucentPassNode,
77    },
78    window::PhysicalSize,
79};
80
81pub(crate) const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType
82
83/// The labels of the default App rendering stages.
84#[derive(Debug, Hash, PartialEq, Eq, Clone)]
85pub enum RenderStageLabel {
86    /// Extract data from the world.
87    Extract,
88
89    /// Prepare render resources from the extracted data for the GPU.
90    /// For example during this phase textures are created, buffers are allocated and written.
91    Prepare,
92
93    /// Queues [PhaseItems](render_phase::PhaseItem) that depend on
94    /// [`Prepare`](RenderStageLabel::Prepare) data and queue up draw calls to run during the
95    /// [`Render`](RenderStageLabel::Render) stage.
96    /// For example data is uploaded to the GPU in this stage.
97    Queue,
98
99    /// Sort the [`RenderPhases`](crate::render_phase::RenderPhase) here.
100    PhaseSort,
101
102    /// Actual rendering happens here.
103    /// In most cases, only the render backend should insert resources here.
104    Render,
105
106    /// Cleanup render resources here.
107    Cleanup,
108}
109
110impl StageLabel for RenderStageLabel {
111    fn dyn_clone(&self) -> Box<dyn StageLabel> {
112        Box::new(self.clone())
113    }
114}
115
116pub struct RenderResources {
117    pub surface: Surface,
118    pub render_target: Eventually<TextureView>,
119    pub depth_texture: Eventually<Texture>,
120    pub multisampling_texture: Eventually<Option<Texture>>,
121}
122
123impl RenderResources {
124    pub fn new(surface: Surface) -> Self {
125        Self {
126            render_target: Default::default(),
127            depth_texture: Default::default(),
128            multisampling_texture: Default::default(),
129            surface,
130        }
131    }
132
133    pub fn recreate_surface<MW>(
134        &mut self,
135        window: &MW,
136        instance: &wgpu::Instance,
137    ) -> Result<(), RenderError>
138    where
139        MW: MapWindow + HeadedMapWindow,
140    {
141        self.surface.recreate::<MW>(window, instance)
142    }
143
144    pub fn surface(&self) -> &Surface {
145        &self.surface
146    }
147}
148
149pub struct Renderer {
150    pub instance: wgpu::Instance,
151    pub device: Arc<wgpu::Device>, // TODO: Arc is needed for headless rendering. Is there a simpler solution?
152    pub queue: wgpu::Queue,
153    pub adapter: wgpu::Adapter,
154
155    pub wgpu_settings: WgpuSettings,
156    pub settings: RendererSettings,
157
158    pub resources: RenderResources,
159    pub render_graph: RenderGraph,
160}
161
162impl Renderer {
163    /// Initializes the renderer by retrieving and preparing the GPU instance, device and queue
164    /// for the specified backend.
165    pub async fn initialize<MW>(
166        window: &MW,
167        wgpu_settings: WgpuSettings,
168        settings: RendererSettings,
169    ) -> Result<Self, RenderError>
170    where
171        MW: MapWindow + HeadedMapWindow,
172    {
173        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
174            backends: wgpu_settings.backends.unwrap_or(wgpu::Backends::all()),
175            flags: Default::default(),
176            dx12_shader_compiler: Default::default(),
177            gles_minor_version: Default::default(),
178        });
179
180        let surface: wgpu::Surface = unsafe {
181            instance
182                .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::from_window(&window.handle())?)?
183        };
184
185        let (adapter, device, queue) = Self::request_device(
186            &instance,
187            &wgpu_settings,
188            &wgpu::RequestAdapterOptions {
189                power_preference: wgpu_settings.power_preference,
190                force_fallback_adapter: false,
191                compatible_surface: Some(&surface),
192            },
193        )
194        .await?;
195
196        let surface = Surface::from_surface(surface, &adapter, window, &settings);
197
198        match surface.head() {
199            Head::Headed(window) => window.configure(&device),
200            Head::Headless(_) => {}
201        }
202
203        Ok(Self {
204            instance,
205            device: Arc::new(device),
206            queue,
207            adapter,
208            wgpu_settings,
209            settings,
210            resources: RenderResources::new(surface),
211            render_graph: Default::default(),
212        })
213    }
214
215    pub async fn initialize_headless<MW>(
216        window: &MW,
217        wgpu_settings: WgpuSettings,
218        settings: RendererSettings,
219    ) -> Result<Self, RenderError>
220    where
221        MW: MapWindow,
222    {
223        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
224            backends: wgpu_settings.backends.unwrap_or(wgpu::Backends::all()),
225            flags: Default::default(),
226            dx12_shader_compiler: Default::default(),
227            gles_minor_version: Default::default(),
228        });
229
230        let (adapter, device, queue) = Self::request_device(
231            &instance,
232            &wgpu_settings,
233            &wgpu::RequestAdapterOptions {
234                power_preference: wgpu_settings.power_preference,
235                force_fallback_adapter: false,
236                compatible_surface: None,
237            },
238        )
239        .await?;
240
241        let surface = Surface::from_image(&device, window, &settings);
242
243        Ok(Self {
244            instance,
245            device: Arc::new(device),
246            queue,
247            adapter,
248            wgpu_settings,
249            settings,
250            resources: RenderResources::new(surface),
251            render_graph: Default::default(),
252        })
253    }
254
255    pub fn resize_surface(&mut self, size: PhysicalSize) {
256        self.resources.surface.resize(size)
257    }
258
259    /// Requests a device
260    async fn request_device(
261        instance: &wgpu::Instance,
262        settings: &WgpuSettings,
263        request_adapter_options: &wgpu::RequestAdapterOptions<'_, '_>,
264    ) -> Result<(wgpu::Adapter, wgpu::Device, wgpu::Queue), RenderError> {
265        let adapter = instance
266            .request_adapter(request_adapter_options)
267            .await
268            .ok_or(RenderError::RequestAdaptor)?;
269
270        let adapter_info = adapter.get_info();
271
272        #[cfg(not(target_arch = "wasm32"))]
273        let trace_path = if settings.record_trace {
274            let path = std::path::Path::new("wgpu_trace");
275            // ignore potential error, wgpu will log it
276            let _ = std::fs::create_dir(path);
277            Some(path)
278        } else {
279            None
280        };
281
282        #[cfg(target_arch = "wasm32")]
283        let trace_path = None;
284
285        let mut features =
286            adapter.features() | wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES;
287        if adapter_info.device_type == wgpu::DeviceType::DiscreteGpu {
288            // `MAPPABLE_PRIMARY_BUFFERS` can have a significant, negative performance impact for
289            // discrete GPUs due to having to transfer data across the PCI-E bus and so it
290            // should not be automatically enabled in this case. It is however beneficial for
291            // integrated GPUs.
292            features -= wgpu::Features::MAPPABLE_PRIMARY_BUFFERS;
293        }
294        let mut limits = adapter.limits();
295
296        // Enforce the disabled features
297        if let Some(disabled_features) = settings.disabled_features {
298            features -= disabled_features;
299        }
300        // NOTE: |= is used here to ensure that any explicitly-enabled features are respected.
301        features |= settings.features;
302
303        // Enforce the limit constraints
304        if let Some(constrained_limits) = settings.constrained_limits.as_ref() {
305            // NOTE: Respect the configured limits as an 'upper bound'. This means for 'max' limits, we
306            // take the minimum of the calculated limits according to the adapter/backend and the
307            // specified max_limits. For 'min' limits, take the maximum instead. This is intended to
308            // err on the side of being conservative. We can't claim 'higher' limits that are supported
309            // but we can constrain to 'lower' limits.
310            limits = wgpu::Limits {
311                max_texture_dimension_1d: limits
312                    .max_texture_dimension_1d
313                    .min(constrained_limits.max_texture_dimension_1d),
314                max_texture_dimension_2d: limits
315                    .max_texture_dimension_2d
316                    .min(constrained_limits.max_texture_dimension_2d),
317                max_texture_dimension_3d: limits
318                    .max_texture_dimension_3d
319                    .min(constrained_limits.max_texture_dimension_3d),
320                max_texture_array_layers: limits
321                    .max_texture_array_layers
322                    .min(constrained_limits.max_texture_array_layers),
323                max_bind_groups: limits
324                    .max_bind_groups
325                    .min(constrained_limits.max_bind_groups),
326                max_bindings_per_bind_group: limits
327                    .max_bindings_per_bind_group
328                    .min(constrained_limits.max_bindings_per_bind_group),
329                max_dynamic_uniform_buffers_per_pipeline_layout: limits
330                    .max_dynamic_uniform_buffers_per_pipeline_layout
331                    .min(constrained_limits.max_dynamic_uniform_buffers_per_pipeline_layout),
332                max_dynamic_storage_buffers_per_pipeline_layout: limits
333                    .max_dynamic_storage_buffers_per_pipeline_layout
334                    .min(constrained_limits.max_dynamic_storage_buffers_per_pipeline_layout),
335                max_sampled_textures_per_shader_stage: limits
336                    .max_sampled_textures_per_shader_stage
337                    .min(constrained_limits.max_sampled_textures_per_shader_stage),
338                max_samplers_per_shader_stage: limits
339                    .max_samplers_per_shader_stage
340                    .min(constrained_limits.max_samplers_per_shader_stage),
341                max_storage_buffers_per_shader_stage: limits
342                    .max_storage_buffers_per_shader_stage
343                    .min(constrained_limits.max_storage_buffers_per_shader_stage),
344                max_storage_textures_per_shader_stage: limits
345                    .max_storage_textures_per_shader_stage
346                    .min(constrained_limits.max_storage_textures_per_shader_stage),
347                max_uniform_buffers_per_shader_stage: limits
348                    .max_uniform_buffers_per_shader_stage
349                    .min(constrained_limits.max_uniform_buffers_per_shader_stage),
350                max_uniform_buffer_binding_size: limits
351                    .max_uniform_buffer_binding_size
352                    .min(constrained_limits.max_uniform_buffer_binding_size),
353                max_storage_buffer_binding_size: limits
354                    .max_storage_buffer_binding_size
355                    .min(constrained_limits.max_storage_buffer_binding_size),
356                max_vertex_buffers: limits
357                    .max_vertex_buffers
358                    .min(constrained_limits.max_vertex_buffers),
359                max_vertex_attributes: limits
360                    .max_vertex_attributes
361                    .min(constrained_limits.max_vertex_attributes),
362                max_vertex_buffer_array_stride: limits
363                    .max_vertex_buffer_array_stride
364                    .min(constrained_limits.max_vertex_buffer_array_stride),
365                max_push_constant_size: limits
366                    .max_push_constant_size
367                    .min(constrained_limits.max_push_constant_size),
368                min_uniform_buffer_offset_alignment: limits
369                    .min_uniform_buffer_offset_alignment
370                    .max(constrained_limits.min_uniform_buffer_offset_alignment),
371                min_storage_buffer_offset_alignment: limits
372                    .min_storage_buffer_offset_alignment
373                    .max(constrained_limits.min_storage_buffer_offset_alignment),
374                max_inter_stage_shader_components: limits
375                    .max_inter_stage_shader_components
376                    .min(constrained_limits.max_inter_stage_shader_components),
377                max_color_attachments: limits
378                    .max_color_attachments
379                    .min(constrained_limits.max_color_attachments),
380                max_color_attachment_bytes_per_sample: limits
381                    .max_color_attachment_bytes_per_sample
382                    .min(constrained_limits.max_color_attachment_bytes_per_sample),
383                max_compute_workgroup_storage_size: limits
384                    .max_compute_workgroup_storage_size
385                    .min(constrained_limits.max_compute_workgroup_storage_size),
386                max_compute_invocations_per_workgroup: limits
387                    .max_compute_invocations_per_workgroup
388                    .min(constrained_limits.max_compute_invocations_per_workgroup),
389                max_compute_workgroup_size_x: limits
390                    .max_compute_workgroup_size_x
391                    .min(constrained_limits.max_compute_workgroup_size_x),
392                max_compute_workgroup_size_y: limits
393                    .max_compute_workgroup_size_y
394                    .min(constrained_limits.max_compute_workgroup_size_y),
395                max_compute_workgroup_size_z: limits
396                    .max_compute_workgroup_size_z
397                    .min(constrained_limits.max_compute_workgroup_size_z),
398                max_compute_workgroups_per_dimension: limits
399                    .max_compute_workgroups_per_dimension
400                    .min(constrained_limits.max_compute_workgroups_per_dimension),
401                min_subgroup_size: 0,
402                max_buffer_size: limits
403                    .max_buffer_size
404                    .min(constrained_limits.max_buffer_size),
405                max_non_sampler_bindings: limits
406                    .max_non_sampler_bindings
407                    .min(constrained_limits.max_non_sampler_bindings),
408                max_subgroup_size: 0,
409            };
410        }
411
412        let (device, queue) = adapter
413            .request_device(
414                &wgpu::DeviceDescriptor {
415                    label: settings.device_label.as_ref().map(|a| a.as_ref()),
416                    required_features: features,
417                    required_limits: limits,
418                    memory_hints: wgpu::MemoryHints::default(),
419                },
420                trace_path,
421            )
422            .await?;
423        Ok((adapter, device, queue))
424    }
425
426    pub fn instance(&self) -> &wgpu::Instance {
427        &self.instance
428    }
429    pub fn device(&self) -> &wgpu::Device {
430        &self.device
431    }
432    pub fn queue(&self) -> &wgpu::Queue {
433        &self.queue
434    }
435    pub fn state(&self) -> &RenderResources {
436        &self.resources
437    }
438    pub fn surface(&self) -> &Surface {
439        &self.resources.surface
440    }
441}
442
443#[cfg(test)]
444#[cfg(not(target_arch = "wasm32"))]
445mod tests {
446    use crate::{
447        tcs::world::World,
448        window::{MapWindow, MapWindowConfig, PhysicalSize, WindowCreateError},
449    };
450
451    #[derive(Clone)]
452    pub struct HeadlessMapWindowConfig {
453        size: PhysicalSize,
454    }
455
456    impl MapWindowConfig for HeadlessMapWindowConfig {
457        type MapWindow = HeadlessMapWindow;
458
459        fn create(&self) -> Result<Self::MapWindow, WindowCreateError> {
460            Ok(Self::MapWindow { size: self.size })
461        }
462    }
463
464    pub struct HeadlessMapWindow {
465        size: PhysicalSize,
466    }
467
468    impl MapWindow for HeadlessMapWindow {
469        fn size(&self) -> PhysicalSize {
470            self.size
471        }
472    }
473
474    #[tokio::test]
475    async fn test_render() {
476        use log::LevelFilter;
477
478        use crate::render::{
479            graph::RenderGraph, graph_runner::RenderGraphRunner, resource::Surface,
480            RenderResources, RendererSettings,
481        };
482
483        let _ = env_logger::builder()
484            .filter_level(LevelFilter::Trace)
485            .is_test(true)
486            .try_init();
487        let graph = RenderGraph::default();
488
489        let backends = wgpu::util::backend_bits_from_env().unwrap_or(wgpu::Backends::all());
490        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
491            backends,
492            flags: Default::default(),
493            dx12_shader_compiler: Default::default(),
494            gles_minor_version: Default::default(),
495        });
496        let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, None)
497            .await
498            .expect("Unable to initialize adapter");
499
500        let (device, queue) = adapter
501            .request_device(
502                &wgpu::DeviceDescriptor {
503                    label: None,
504                    required_features: wgpu::Features::default(),
505                    required_limits: wgpu::Limits::default(),
506                    memory_hints: wgpu::MemoryHints::default(),
507                },
508                None,
509            )
510            .await
511            .expect("Unable to request device");
512
513        let render_state = RenderResources::new(Surface::from_image(
514            &device,
515            &HeadlessMapWindow {
516                size: PhysicalSize::new(100, 100).expect("invalid headless map size"),
517            },
518            &RendererSettings::default(),
519        ));
520
521        let world = World::default();
522        RenderGraphRunner::run(&graph, &device, &queue, &render_state, &world)
523            .expect("failed to run graph runner");
524    }
525}
526
527// Contributors to the RenderGraph should use the following label conventions:
528// 1. Graph modules should have a NAME, input module, and node module (where relevant)
529// 2. The "main_graph" graph is the root.
530// 3. "sub graph" modules should be nested beneath their parent graph module
531pub mod main_graph {
532    // Labels for input nodes
533    pub mod input {}
534    // Labels for non-input nodes
535    pub mod node {
536        pub const MAIN_PASS_DEPENDENCIES: &str = "main_pass_dependencies";
537        pub const MAIN_PASS_DRIVER: &str = "main_pass_driver";
538    }
539}
540
541/// Labels for the "draw" graph
542mod draw_graph {
543    pub const NAME: &str = "draw";
544    // Labels for input nodes
545    pub mod input {}
546    // Labels for non-input nodes
547    pub mod node {
548        pub const MAIN_PASS: &str = "main_pass";
549        pub const TRANSLUCENT_PASS: &str = "translucent_pass";
550    }
551}
552
553pub struct MaskPipeline(pub wgpu::RenderPipeline);
554impl Deref for MaskPipeline {
555    type Target = wgpu::RenderPipeline;
556
557    fn deref(&self) -> &Self::Target {
558        &self.0
559    }
560}
561
562// TODO: Do we really want a render plugin or do we want to statically do this setup?
563#[derive(Default)]
564pub struct RenderPlugin;
565
566impl<E: Environment> Plugin<E> for RenderPlugin {
567    fn build(
568        &self,
569        schedule: &mut Schedule,
570        _kernel: Rc<Kernel<E>>,
571        world: &mut World,
572        graph: &mut RenderGraph,
573    ) {
574        let resources = &mut world.resources;
575
576        let mut draw_graph = RenderGraph::default();
577        // Draw nodes
578        draw_graph.add_node(draw_graph::node::MAIN_PASS, MainPassNode::new());
579        // Draw nodes
580        draw_graph.add_node(
581            draw_graph::node::TRANSLUCENT_PASS,
582            TranslucentPassNode::new(),
583        );
584        // Input node
585        let input_node_id = draw_graph.set_input(vec![]);
586        // Edges
587        draw_graph
588            .add_node_edge(input_node_id, draw_graph::node::MAIN_PASS)
589            .expect("main pass or draw node does not exist");
590        draw_graph
591            .add_node_edge(
592                draw_graph::node::MAIN_PASS,
593                draw_graph::node::TRANSLUCENT_PASS,
594            )
595            .expect("main pass or draw node does not exist");
596
597        graph.add_sub_graph(draw_graph::NAME, draw_graph);
598        graph.add_node(main_graph::node::MAIN_PASS_DEPENDENCIES, EmptyNode);
599        graph.add_node(main_graph::node::MAIN_PASS_DRIVER, MainPassDriverNode);
600        graph
601            .add_node_edge(
602                main_graph::node::MAIN_PASS_DEPENDENCIES,
603                main_graph::node::MAIN_PASS_DRIVER,
604            )
605            .expect("main pass driver or dependencies do not exist");
606
607        // render graph dependency
608        resources.init::<RenderPhase<LayerItem>>();
609        resources.init::<RenderPhase<TileMaskItem>>();
610        resources.init::<RenderPhase<TranslucentItem>>();
611        // tile_view_pattern:
612        resources.insert(Eventually::<WgpuTileViewPattern>::Uninitialized);
613        resources.init::<ViewTileSources>();
614        // masks
615        resources.insert(Eventually::<MaskPipeline>::Uninitialized);
616
617        schedule.add_stage(RenderStageLabel::Extract, SystemStage::default());
618        schedule.add_stage(
619            RenderStageLabel::Prepare,
620            SystemStage::default().with_system(SystemContainer::new(ResourceSystem)),
621        );
622        schedule.add_stage(
623            RenderStageLabel::Queue,
624            SystemStage::default()
625                .with_system(tile_view_pattern_system)
626                .with_system(upload_system),
627        );
628        schedule.add_stage(
629            RenderStageLabel::PhaseSort,
630            SystemStage::default().with_system(sort_phase_system),
631        );
632        schedule.add_stage(
633            RenderStageLabel::Render,
634            SystemStage::default().with_system(SystemContainer::new(GraphRunnerSystem)),
635        );
636        schedule.add_stage(
637            RenderStageLabel::Cleanup,
638            SystemStage::default().with_system(cleanup_system),
639        );
640    }
641}