diff --git a/maplibre-demo/Cargo.toml b/maplibre-demo/Cargo.toml index ee6d315b..4a2fd5c2 100644 --- a/maplibre-demo/Cargo.toml +++ b/maplibre-demo/Cargo.toml @@ -14,13 +14,8 @@ trace = ["maplibre/trace", "tracing-subscriber", "tracing-tracy", "tracy-client" [dependencies] env_logger = "0.9" -maplibre = { path = "../maplibre", version = "0.0.2" } +maplibre = { path = "../maplibre", version = "0.0.2", features = ["headless"] } maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" } -geozero = { version = "0.9.4", default-features = false, features = ["with-mvt", "with-geo"]} - - -tokio = "1.18" -wgpu = "0.12" tracing = { version = "0.1" } tracing-subscriber = { version = "0.3", optional = true } diff --git a/maplibre-demo/src/main.rs b/maplibre-demo/src/main.rs index 9a98be72..609926e0 100644 --- a/maplibre-demo/src/main.rs +++ b/maplibre-demo/src/main.rs @@ -1,4 +1,3 @@ -use geozero::mvt::tile; use maplibre::benchmarking::tessellation::{IndexDataType, OverAlignedVertexBuffer}; use maplibre::coords::WorldTileCoords; use maplibre::error::Error; @@ -8,19 +7,18 @@ use maplibre::io::pipeline_steps::build_vector_tile_pipeline; use maplibre::io::scheduler::ScheduleMethod; use maplibre::io::source_client::{HttpClient, HttpSourceClient}; use maplibre::io::tile_repository::StoredLayer; -use maplibre::io::{TileRequest, TileRequestID}; +use maplibre::io::{RawLayer, TileRequest, TileRequestID}; use maplibre::map_schedule::{EventuallyMapContext, InteractiveMapSchedule}; use maplibre::platform::http_client::ReqwestHttpClient; use maplibre::platform::run_multithreaded; use maplibre::platform::schedule_method::TokioScheduleMethod; -use maplibre::render::settings::RendererSettings; +use maplibre::render::settings::{RendererSettings, TextureFormat}; use maplibre::render::ShaderVertex; use maplibre::window::{EventLoop, MapWindow, MapWindowConfig, WindowSize}; use maplibre::MapBuilder; use maplibre_winit::winit::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow}; use std::any::Any; use std::collections::HashSet; -use wgpu::TextureFormat; #[cfg(feature = "trace")] fn enable_tracing() { @@ -77,7 +75,7 @@ impl PipelineProcessor for HeadlessPipelineProcessor { coords: &WorldTileCoords, buffer: OverAlignedVertexBuffer, feature_indices: Vec, - layer_data: tile::Layer, + layer_data: RawLayer, ) { self.layers.push(StoredLayer::TessellatedLayer { coords: *coords, diff --git a/maplibre/Cargo.toml b/maplibre/Cargo.toml index a8605835..ac9de778 100644 --- a/maplibre/Cargo.toml +++ b/maplibre/Cargo.toml @@ -14,6 +14,7 @@ web-webgl = ["wgpu/webgl"] trace = [ "tracing-subscriber", "tracing-tracy", "tracy-client"] no-thread-safe-futures = [] embed-static-tiles = ["maplibre-build-tools/sqlite"] +headless = ["png"] [target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "linux", target_os = "android", target_os = "windows"))'.dependencies] @@ -33,24 +34,25 @@ reqwest = { version = "0.11", default-features = false, features = ["rustls-tls" async-trait = "0.1" instant = { version = "0.1", features = ["wasm-bindgen"] } # FIXME: Untrusted dependency -raw-window-handle = "0.4" - +# Tracing tracing = { version = "0.1" } tracing-subscriber = { version = "0.3", optional = true } +# Maths cgmath = "0.18" +# Geo geo = { version = "0.19" } geo-types = { version = "0.7", features = ["use-rstar_0_9"] } rstar = { version = "0.9" } prost = "0.10.1" geozero = { version = "0.9.4", default-features = false, features = ["with-mvt", "with-geo"]} - tile-grid = "0.3" # Rendering wgpu = { version = "0.12" } lyon = { version = "0.17", features = [] } +raw-window-handle = "0.4" # cached = "0.32" @@ -61,18 +63,24 @@ log = "0.4" bytemuck = "1.2.0" bytemuck_derive = "1.0" +# Static tiles inclusion include_dir = "0.7.2" +# JSON serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" + +# Colors csscolorparser = { version = "0.5", features = ["serde", "cint"]} cint = "0.2" +# Required by bevy renderer thiserror = "1" downcast-rs = "1.2" smallvec = "1.8" -png = "0.17" +# Headless +png = { version = "0.17", optional = true } [build-dependencies] maplibre-build-tools = { path = "../maplibre-build-tools", version = "0.1.0", features = ["sqlite"] } diff --git a/maplibre/src/context.rs b/maplibre/src/context.rs index 68d5e2a9..3892fb8e 100644 --- a/maplibre/src/context.rs +++ b/maplibre/src/context.rs @@ -55,6 +55,7 @@ impl ViewState { } } +/// Stores the context of the map. pub struct MapContext { pub view_state: ViewState, pub style: Style, diff --git a/maplibre/src/io/mod.rs b/maplibre/src/io/mod.rs index 4e73b91f..600ca111 100644 --- a/maplibre/src/io/mod.rs +++ b/maplibre/src/io/mod.rs @@ -1,14 +1,10 @@ //! Handles IO related processing as well as multithreading. use crate::coords::WorldTileCoords; - -use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer}; - use crate::render::ShaderVertex; -use geozero::mvt::tile; +use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer}; use std::collections::HashSet; use std::fmt; - pub mod scheduler; pub mod source_client; pub mod static_tile_fetcher; @@ -19,6 +15,8 @@ pub mod pipeline_steps; pub mod tile_repository; pub mod tile_request_state; +pub use geozero::mvt::tile::Layer as RawLayer; + /// A request for a tile at the given coordinates and in the given layers. #[derive(Clone)] pub struct TileRequest { diff --git a/maplibre/src/lib.rs b/maplibre/src/lib.rs index 6790e5e1..146aaad4 100644 --- a/maplibre/src/lib.rs +++ b/maplibre/src/lib.rs @@ -160,7 +160,7 @@ where #[cfg(target_os = "android")] let renderer = None; #[cfg(not(target_os = "android"))] - let renderer = Renderer::initialize::( + let renderer = Renderer::initialize( &window, self.wgpu_settings.clone(), self.renderer_settings.clone(), @@ -186,7 +186,7 @@ where let window = self.map_window_config.create(); let window_size = window.size(); - let renderer = Renderer::initialize_headless::( + let renderer = Renderer::initialize_headless( &window, self.wgpu_settings.clone(), self.renderer_settings.clone(), diff --git a/maplibre/src/map_schedule.rs b/maplibre/src/map_schedule.rs index d38eb3b0..6fd94a4e 100644 --- a/maplibre/src/map_schedule.rs +++ b/maplibre/src/map_schedule.rs @@ -1,5 +1,3 @@ -//! Stores the state of the map such as `[crate::coords::Zoom]`, `[crate::camera::Camera]`, `[crate::style::Style]`, `[crate::io::tile_repository::TileCache]` and more. - use crate::context::{MapContext, ViewState}; use crate::error::Error; use crate::io::geometry_index::GeometryIndex; @@ -162,8 +160,7 @@ where let mut renderer = &mut map_context.renderer; renderer .state - .surface - .recreate::(window, &renderer.instance); + .recreate_surface::(window, &renderer.instance); self.suspended = false; } } @@ -187,13 +184,10 @@ where .. }) => { let window = self.map_window_config.create(); - let renderer = Renderer::initialize::( - &window, - wgpu_settings.clone(), - renderer_settings.clone(), - ) - .await - .unwrap(); + let renderer = + Renderer::initialize(&window, wgpu_settings.clone(), renderer_settings.clone()) + .await + .unwrap(); self.map_context.make_full(renderer); true } diff --git a/maplibre/src/render/mod.rs b/maplibre/src/render/mod.rs index 8aadcf65..9384aac9 100644 --- a/maplibre/src/render/mod.rs +++ b/maplibre/src/render/mod.rs @@ -32,7 +32,7 @@ use log::info; use std::sync::Arc; // Rendering internals -#[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "headless")] mod copy_surface_to_buffer_node; pub mod graph; mod graph_runner; @@ -78,7 +78,7 @@ pub struct RenderState { depth_texture: Eventually, multisampling_texture: Eventually>, - pub surface: Surface, + surface: Surface, mask_phase: RenderPhase, tile_phase: RenderPhase<(IndexEntry, TileShape)>, @@ -100,11 +100,18 @@ impl RenderState { tile_phase: Default::default(), } } + + pub fn recreate_surface(&mut self, window: &MW, instance: &wgpu::Instance) + where + MW: MapWindow + HeadedMapWindow, + { + self.surface.recreate::(window, instance); + } } pub struct Renderer { pub instance: wgpu::Instance, - pub device: Arc, + pub device: Arc, // TODO: Arc is needed for headless rendering. Is there a simpler solution? pub queue: wgpu::Queue, pub adapter_info: wgpu::AdapterInfo, @@ -117,18 +124,17 @@ pub struct Renderer { impl Renderer { /// Initializes the renderer by retrieving and preparing the GPU instance, device and queue /// for the specified backend. - pub async fn initialize( - window: &MWC::MapWindow, + pub async fn initialize( + window: &MW, wgpu_settings: WgpuSettings, settings: RendererSettings, ) -> Result where - MWC: MapWindowConfig, - ::MapWindow: HeadedMapWindow, + MW: MapWindow + HeadedMapWindow, { let instance = wgpu::Instance::new(wgpu_settings.backends.unwrap_or(wgpu::Backends::all())); - let surface = Surface::from_window::(&instance, window, &settings); + let surface = Surface::from_window(&instance, window, &settings); let compatible_surface = match &surface.head() { Head::Headed(window_head) => Some(window_head.surface()), @@ -162,13 +168,13 @@ impl Renderer { }) } - pub async fn initialize_headless( - window: &MWC::MapWindow, + pub async fn initialize_headless( + window: &MW, wgpu_settings: WgpuSettings, settings: RendererSettings, ) -> Result where - MWC: MapWindowConfig, + MW: MapWindow, { let instance = wgpu::Instance::new(wgpu_settings.backends.unwrap_or(wgpu::Backends::all())); @@ -183,7 +189,7 @@ impl Renderer { ) .await?; - let surface = Surface::from_image::(&device, window, &settings); + let surface = Surface::from_image(&device, window, &settings); Ok(Self { instance, @@ -440,9 +446,9 @@ mod tests { } } -// Plugins that contribute to the RenderGraph should use the following label conventions: +// Contributors to the RenderGraph should use the following label conventions: // 1. Graph modules should have a NAME, input module, and node module (where relevant) -// 2. The "top level" graph is the plugin module root. Just add things like `pub mod node` directly under the plugin module +// 2. The "main_graph" graph is the root. // 3. "sub graph" modules should be nested beneath their parent graph module pub mod main_graph { // Labels for input nodes @@ -462,7 +468,7 @@ pub mod draw_graph { // Labels for non-input nodes pub mod node { pub const MAIN_PASS: &str = "main_pass"; - #[cfg(not(target_arch = "wasm32"))] + #[cfg(feature = "headless")] pub const COPY: &str = "copy"; } } diff --git a/maplibre/src/render/resource/surface.rs b/maplibre/src/render/resource/surface.rs index 14e1419f..a0effb90 100644 --- a/maplibre/src/render/resource/surface.rs +++ b/maplibre/src/render/resource/surface.rs @@ -44,10 +44,9 @@ impl WindowHead { self.surface.configure(device, &self.surface_config); } - pub fn recreate_surface(&mut self, window: &MWC::MapWindow, instance: &wgpu::Instance) + pub fn recreate_surface(&mut self, window: &MW, instance: &wgpu::Instance) where - MWC: MapWindowConfig, - ::MapWindow: HeadedMapWindow, + MW: MapWindow + HeadedMapWindow, { self.surface = unsafe { instance.create_surface(window.inner()) }; } @@ -121,14 +120,13 @@ pub struct Surface { } impl Surface { - pub fn from_window( + pub fn from_window( instance: &wgpu::Instance, - window: &MWC::MapWindow, + window: &MW, settings: &RendererSettings, ) -> Self where - MWC: MapWindowConfig, - ::MapWindow: HeadedMapWindow, + MW: MapWindow + HeadedMapWindow, { let size = window.size(); let surface_config = wgpu::SurfaceConfiguration { @@ -151,13 +149,10 @@ impl Surface { } } - pub fn from_image( - device: &wgpu::Device, - window: &MWC::MapWindow, - settings: &RendererSettings, - ) -> Self + // TODO: Give better name + pub fn from_image(device: &wgpu::Device, window: &MW, settings: &RendererSettings) -> Self where - MWC: MapWindowConfig, + MW: MapWindow, { let size = window.size(); @@ -250,15 +245,14 @@ impl Surface { } } - pub fn recreate(&mut self, window: &MWC::MapWindow, instance: &wgpu::Instance) + pub fn recreate(&mut self, window: &MW, instance: &wgpu::Instance) where - MWC: MapWindowConfig, - ::MapWindow: HeadedMapWindow, + MW: MapWindow + HeadedMapWindow, { match &mut self.head { Head::Headed(window_head) => { if window_head.has_changed(&(self.size.width(), self.size.height())) { - window_head.recreate_surface::(window, instance); + window_head.recreate_surface(window, instance); } } Head::Headless(_) => {} diff --git a/maplibre/src/render/settings.rs b/maplibre/src/render/settings.rs index fd10faea..479039d7 100644 --- a/maplibre/src/render/settings.rs +++ b/maplibre/src/render/settings.rs @@ -4,6 +4,10 @@ use crate::platform::COLOR_TEXTURE_FORMAT; use std::borrow::Cow; pub use wgpu::Backends; +pub use wgpu::Features; +pub use wgpu::Limits; +pub use wgpu::PowerPreference; +pub use wgpu::TextureFormat; /// Provides configuration for renderer initialization. Use [`Device::features`](crate::renderer::Device::features), /// [`Device::limits`](crate::renderer::Device::limits), and the [`WgpuAdapterInfo`](crate::render_resource::WgpuAdapterInfo) @@ -11,17 +15,17 @@ pub use wgpu::Backends; #[derive(Clone)] pub struct WgpuSettings { pub device_label: Option>, - pub backends: Option, - pub power_preference: wgpu::PowerPreference, + pub backends: Option, + pub power_preference: PowerPreference, /// The features to ensure are enabled regardless of what the adapter/backend supports. /// Setting these explicitly may cause renderer initialization to fail. - pub features: wgpu::Features, + pub features: Features, /// The features to ensure are disabled regardless of what the adapter/backend supports - pub disabled_features: Option, + pub disabled_features: Option, /// The imposed limits. - pub limits: wgpu::Limits, + pub limits: Limits, /// The constraints on limits allowed regardless of what the adapter/backend supports - pub constrained_limits: Option, + pub constrained_limits: Option, /// Whether a trace is recorded an stored in the current working directory pub record_trace: bool, @@ -32,12 +36,12 @@ impl Default for WgpuSettings { let backends = Some(wgpu::util::backend_bits_from_env().unwrap_or(wgpu::Backends::all())); let limits = if cfg!(feature = "web-webgl") { - wgpu::Limits { + Limits { max_texture_dimension_2d: 4096, - ..wgpu::Limits::downlevel_webgl2_defaults() + ..Limits::downlevel_webgl2_defaults() } } else if cfg!(target_os = "android") { - wgpu::Limits { + Limits { max_storage_textures_per_shader_stage: 4, max_compute_workgroups_per_dimension: 0, max_compute_workgroup_size_z: 0, @@ -45,19 +49,19 @@ impl Default for WgpuSettings { max_compute_workgroup_size_x: 0, max_compute_workgroup_storage_size: 0, max_compute_invocations_per_workgroup: 0, - ..wgpu::Limits::downlevel_defaults() + ..Limits::downlevel_defaults() } } else { - wgpu::Limits { - ..wgpu::Limits::default() + Limits { + ..Limits::default() } }; Self { device_label: Default::default(), backends, - power_preference: wgpu::PowerPreference::HighPerformance, - features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, + power_preference: PowerPreference::HighPerformance, + features: Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, disabled_features: None, limits, constrained_limits: None, @@ -101,7 +105,7 @@ impl Default for Msaa { #[derive(Clone)] pub struct RendererSettings { pub msaa: Msaa, - pub texture_format: wgpu::TextureFormat, + pub texture_format: TextureFormat, } impl Default for RendererSettings { diff --git a/maplibre/src/render/stages/mod.rs b/maplibre/src/render/stages/mod.rs index 676644d4..5e73dfab 100644 --- a/maplibre/src/render/stages/mod.rs +++ b/maplibre/src/render/stages/mod.rs @@ -19,7 +19,7 @@ mod phase_sort_stage; mod queue_stage; mod resource_stage; mod upload_stage; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "headless")] mod write_surface_buffer_stage; /// The labels of the default App rendering stages. @@ -69,7 +69,7 @@ pub fn register_render_stages( let input_node_id = draw_graph.set_input(vec![]); draw_graph.add_node_edge(input_node_id, draw_graph::node::MAIN_PASS)?; - #[cfg(not(target_arch = "wasm32"))] + #[cfg(feature = "headless")] if headless { use crate::render::copy_surface_to_buffer_node::CopySurfaceBufferNode; draw_graph.add_node(draw_graph::node::COPY, CopySurfaceBufferNode::default()); @@ -89,7 +89,7 @@ pub fn register_render_stages( schedule.add_stage(RenderStageLabel::PhaseSort, PhaseSortStage::default()); schedule.add_stage(RenderStageLabel::Render, GraphRunnerStage::new(graph)); - #[cfg(not(target_arch = "wasm32"))] + #[cfg(feature = "headless")] if headless { use crate::render::stages::write_surface_buffer_stage::WriteSurfaceBufferStage; schedule.add_stage( diff --git a/maplibre/src/stages/populate_tile_store_stage.rs b/maplibre/src/stages/populate_tile_store_stage.rs index 54064a6d..17d69382 100644 --- a/maplibre/src/stages/populate_tile_store_stage.rs +++ b/maplibre/src/stages/populate_tile_store_stage.rs @@ -1,4 +1,4 @@ -//! Receives data from async threads and populates the [`crate::io::tile_repository::TileCache`]. +//! Receives data from async threads and populates the [`crate::io::tile_repository::TileRepository`]. use super::{MessageReceiver, SharedThreadState, TessellateMessage, TileTessellateMessage}; use crate::context::MapContext;