From a3894e370c2463e114e7416f05154cb7122c9de8 Mon Sep 17 00:00:00 2001 From: Max Ammann Date: Sat, 27 Jul 2024 23:10:43 +0200 Subject: [PATCH 01/78] Update idea file --- .idea/maplibre-rs.iml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.idea/maplibre-rs.iml b/.idea/maplibre-rs.iml index c67bd1d39..c33d4b0c6 100644 --- a/.idea/maplibre-rs.iml +++ b/.idea/maplibre-rs.iml @@ -1,21 +1,15 @@ - + - - - - - - - - + + From 30aa18740c24491453fbeccdbf4dddce6255f570 Mon Sep 17 00:00:00 2001 From: Max Ammann Date: Sat, 27 Jul 2024 23:11:03 +0200 Subject: [PATCH 02/78] Add some expect calls --- maplibre/src/raster/request_system.rs | 4 ++-- maplibre/src/render/view_state.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/maplibre/src/raster/request_system.rs b/maplibre/src/raster/request_system.rs index 410d73b1f..bb5da359b 100644 --- a/maplibre/src/raster/request_system.rs +++ b/maplibre/src/raster/request_system.rs @@ -72,7 +72,7 @@ impl System for RequestSystem { world .tiles .spawn_mut(coords) - .unwrap() + .expect("unable to spawn a raster tile") .insert(RasterLayersDataComponent::default()); tracing::event!(tracing::Level::ERROR, %coords, "tile request started: {coords}"); @@ -93,7 +93,7 @@ impl System for RequestSystem { >>::Context, >, ) - .unwrap(); // TODO: Remove unwrap + .expect("unable to call APC"); // TODO: Remove unwrap } } } diff --git a/maplibre/src/render/view_state.rs b/maplibre/src/render/view_state.rs index 42fadeaea..a52388a79 100644 --- a/maplibre/src/render/view_state.rs +++ b/maplibre/src/render/view_state.rs @@ -180,7 +180,7 @@ impl ViewState { // https://www.sjbaker.org/steve/omniv/love_your_z_buffer.html let near_z = height / 50.0; - let mut perspective = + let perspective = self.perspective .calc_matrix_with_center(width, height, near_z, far_z, center_offset); From 4edde0921ff43f4792b79512dc4bc9899fdb1abd Mon Sep 17 00:00:00 2001 From: Max Ammann Date: Sat, 27 Jul 2024 23:11:13 +0200 Subject: [PATCH 03/78] Correct comment in shell.nix --- shell.nix | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/shell.nix b/shell.nix index cfcefae2e..4f7d29412 100644 --- a/shell.nix +++ b/shell.nix @@ -1,4 +1,4 @@ -# This nix-shell only supports macOS right now. Soon I will also add support for Linux +# This nix-shell supports macOS and Linux. # The repository supports direnv (https://direnv.net/). If your IDE supports direnv, # then you do not need to care about dependencies. @@ -50,10 +50,12 @@ in unstable.wayland ]; shellHook = '' - export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${ pkgs.lib.makeLibraryPath [ unstable.libxkbcommon ] }"; - # Vulkan - export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${ pkgs.lib.makeLibraryPath [ pkgs.vulkan-loader ] }"; - # EGL - export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${ pkgs.lib.makeLibraryPath [ pkgs.libglvnd ] }"; + export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${ pkgs.lib.makeLibraryPath [ + unstable.libxkbcommon + # Vulkan + pkgs.vulkan-loader + # EGL + pkgs.libglvnd + ] }"; ''; } From e74125b62c94454976cee5290cec415590ff0b06 Mon Sep 17 00:00:00 2001 From: Max Ammann Date: Sat, 27 Jul 2024 23:19:14 +0200 Subject: [PATCH 04/78] Move tesselation code to vector module --- maplibre/src/benchmarking.rs | 2 +- maplibre/src/lib.rs | 3 - maplibre/src/tessellation/mod.rs | 112 ---------------- maplibre/src/vector/mod.rs | 5 +- maplibre/src/vector/process_vector.rs | 2 +- maplibre/src/vector/resource/buffer_pool.rs | 2 +- .../tessellation.rs} | 123 ++++++++++++++++-- maplibre/src/vector/transferables.rs | 2 +- 8 files changed, 122 insertions(+), 129 deletions(-) delete mode 100644 maplibre/src/tessellation/mod.rs rename maplibre/src/{tessellation/zero_tessellator.rs => vector/tessellation.rs} (60%) diff --git a/maplibre/src/benchmarking.rs b/maplibre/src/benchmarking.rs index a1bb6cece..a5f8ebea4 100644 --- a/maplibre/src/benchmarking.rs +++ b/maplibre/src/benchmarking.rs @@ -7,5 +7,5 @@ pub mod io { /// Re-export of the tessellation module. pub mod tessellation { - pub use crate::tessellation::*; + pub use crate::vector::tessellation::*; } diff --git a/maplibre/src/lib.rs b/maplibre/src/lib.rs index 44cb089a8..a57e465ea 100644 --- a/maplibre/src/lib.rs +++ b/maplibre/src/lib.rs @@ -23,9 +23,6 @@ extern crate core; // Export tile format pub use geozero::mvt::tile; // Used in transferables.rs in web/singlethreaded -// Internal modules -pub(crate) mod tessellation; - pub mod context; pub mod coords; #[cfg(feature = "headless")] diff --git a/maplibre/src/tessellation/mod.rs b/maplibre/src/tessellation/mod.rs deleted file mode 100644 index 56bfce8be..000000000 --- a/maplibre/src/tessellation/mod.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! Tessellation for lines and polygons is implemented here. - -use bytemuck::Pod; -use lyon::tessellation::{ - FillVertex, FillVertexConstructor, StrokeVertex, StrokeVertexConstructor, VertexBuffers, -}; - -use crate::render::ShaderVertex; - -pub mod zero_tessellator; - -const DEFAULT_TOLERANCE: f32 = 0.02; - -/// Vertex buffers index data type. -pub type IndexDataType = u32; // Must match INDEX_FORMAT - -/// Constructor for Fill and Stroke vertices. -pub struct VertexConstructor {} - -impl FillVertexConstructor for VertexConstructor { - fn new_vertex(&mut self, vertex: FillVertex) -> ShaderVertex { - ShaderVertex::new(vertex.position().to_array(), [0.0, 0.0]) - } -} - -impl StrokeVertexConstructor for VertexConstructor { - fn new_vertex(&mut self, vertex: StrokeVertex) -> ShaderVertex { - ShaderVertex::new( - vertex.position_on_path().to_array(), - vertex.normal().to_array(), - ) - } -} - -/// Vertex buffer which includes additional padding to fulfill the `wgpu::COPY_BUFFER_ALIGNMENT`. -#[derive(Clone)] -pub struct OverAlignedVertexBuffer { - pub buffer: VertexBuffers, - pub usable_indices: u32, -} - -impl OverAlignedVertexBuffer { - pub fn empty() -> Self { - Self { - buffer: VertexBuffers::with_capacity(0, 0), - usable_indices: 0, - } - } - - pub fn from_iters(vertices: IV, indices: II, usable_indices: u32) -> Self - where - IV: IntoIterator, - II: IntoIterator, - IV::IntoIter: ExactSizeIterator, - II::IntoIter: ExactSizeIterator, - { - let vertices = vertices.into_iter(); - let indices = indices.into_iter(); - let mut buffers = VertexBuffers::with_capacity(vertices.len(), indices.len()); - buffers.vertices.extend(vertices); - buffers.indices.extend(indices); - Self { - buffer: buffers, - usable_indices, - } - } -} - -impl From> for OverAlignedVertexBuffer { - fn from(mut buffer: VertexBuffers) -> Self { - let usable_indices = buffer.indices.len() as u32; - buffer.align_vertices(); - buffer.align_indices(); - Self { - buffer, - usable_indices, - } - } -} - -trait Align { - fn align_vertices(&mut self); - fn align_indices(&mut self); -} - -impl Align for VertexBuffers { - fn align_vertices(&mut self) { - let align = wgpu::COPY_BUFFER_ALIGNMENT; - let stride = std::mem::size_of::() as wgpu::BufferAddress; - let unpadded_bytes = self.vertices.len() as wgpu::BufferAddress * stride; - let padding_bytes = (align - unpadded_bytes % align) % align; - - if padding_bytes != 0 { - panic!( - "vertices are always aligned to wgpu::COPY_BUFFER_ALIGNMENT \ - because GpuVertexUniform is aligned" - ) - } - } - - fn align_indices(&mut self) { - let align = wgpu::COPY_BUFFER_ALIGNMENT; - let stride = std::mem::size_of::() as wgpu::BufferAddress; - let unpadded_bytes = self.indices.len() as wgpu::BufferAddress * stride; - let padding_bytes = (align - unpadded_bytes % align) % align; - let overpad = (padding_bytes + stride - 1) / stride; // Divide by stride but round up - - for _ in 0..overpad { - self.indices.push(I::zeroed()); - } - } -} diff --git a/maplibre/src/vector/mod.rs b/maplibre/src/vector/mod.rs index 71d807dff..4f6925547 100644 --- a/maplibre/src/vector/mod.rs +++ b/maplibre/src/vector/mod.rs @@ -13,7 +13,7 @@ use crate::{ }, schedule::Schedule, tcs::{system::SystemContainer, tiles::TileComponent, world::World}, - tessellation::{IndexDataType, OverAlignedVertexBuffer}, + vector::tessellation::{IndexDataType, OverAlignedVertexBuffer}, vector::{ populate_world_system::PopulateWorldSystem, queue_system::queue_system, request_system::RequestSystem, resource::BufferPool, resource_system::resource_system, @@ -31,6 +31,9 @@ mod resource_system; mod transferables; mod upload_system; +// Public due to bechmarks +pub mod tessellation; + pub use process_vector::*; pub use transferables::{ DefaultVectorTransferables, LayerIndexed, LayerMissing, LayerTessellated, TileTessellated, diff --git a/maplibre/src/vector/process_vector.rs b/maplibre/src/vector/process_vector.rs index 79ff7a3bc..aa74ecdc1 100644 --- a/maplibre/src/vector/process_vector.rs +++ b/maplibre/src/vector/process_vector.rs @@ -13,7 +13,7 @@ use crate::{ geometry_index::{IndexProcessor, IndexedGeometry, TileIndex}, }, render::ShaderVertex, - tessellation::{zero_tessellator::ZeroTessellator, IndexDataType, OverAlignedVertexBuffer}, + vector::tessellation::{ZeroTessellator, IndexDataType, OverAlignedVertexBuffer}, vector::transferables::{ LayerIndexed, LayerMissing, LayerTessellated, TileTessellated, VectorTransferables, }, diff --git a/maplibre/src/vector/resource/buffer_pool.rs b/maplibre/src/vector/resource/buffer_pool.rs index 9a92b279a..777f5edb9 100644 --- a/maplibre/src/vector/resource/buffer_pool.rs +++ b/maplibre/src/vector/resource/buffer_pool.rs @@ -18,7 +18,7 @@ use crate::{ }, style::layer::StyleLayer, tcs::world::World, - tessellation::OverAlignedVertexBuffer, + vector::tessellation::OverAlignedVertexBuffer, }; // TODO: Too low values can cause a back-and-forth between unloading and loading layers diff --git a/maplibre/src/tessellation/zero_tessellator.rs b/maplibre/src/vector/tessellation.rs similarity index 60% rename from maplibre/src/tessellation/zero_tessellator.rs rename to maplibre/src/vector/tessellation.rs index 8502373f3..79ee502b1 100644 --- a/maplibre/src/tessellation/zero_tessellator.rs +++ b/maplibre/src/vector/tessellation.rs @@ -1,11 +1,10 @@ -//! Tessellator implementation. +//! Tessellation for lines and polygons is implemented here. use std::cell::RefCell; use geozero::{FeatureProcessor, GeomProcessor, PropertyProcessor}; use lyon::{ geom, - lyon_tessellation::VertexBuffers, path::{path::Builder, Path}, tessellation::{ geometry_builder::MaxIndex, BuffersBuilder, FillOptions, FillRule, FillTessellator, @@ -15,9 +14,116 @@ use lyon::{ use crate::{ render::ShaderVertex, - tessellation::{VertexConstructor, DEFAULT_TOLERANCE}, }; +use bytemuck::Pod; +use lyon::tessellation::{ + FillVertex, FillVertexConstructor, StrokeVertex, StrokeVertexConstructor, VertexBuffers, +}; + +const DEFAULT_TOLERANCE: f32 = 0.02; + +/// Vertex buffers index data type. +pub type IndexDataType = u32; // Must match INDEX_FORMAT + +/// Constructor for Fill and Stroke vertices. +pub struct VertexConstructor {} + +impl FillVertexConstructor for VertexConstructor { + fn new_vertex(&mut self, vertex: FillVertex) -> ShaderVertex { + ShaderVertex::new(vertex.position().to_array(), [0.0, 0.0]) + } +} + +impl StrokeVertexConstructor for VertexConstructor { + fn new_vertex(&mut self, vertex: StrokeVertex) -> ShaderVertex { + ShaderVertex::new( + vertex.position_on_path().to_array(), + vertex.normal().to_array(), + ) + } +} + +/// Vertex buffer which includes additional padding to fulfill the `wgpu::COPY_BUFFER_ALIGNMENT`. +#[derive(Clone)] +pub struct OverAlignedVertexBuffer { + pub buffer: VertexBuffers, + pub usable_indices: u32, +} + +impl OverAlignedVertexBuffer { + pub fn empty() -> Self { + Self { + buffer: VertexBuffers::with_capacity(0, 0), + usable_indices: 0, + } + } + + pub fn from_iters(vertices: IV, indices: II, usable_indices: u32) -> Self + where + IV: IntoIterator, + II: IntoIterator, + IV::IntoIter: ExactSizeIterator, + II::IntoIter: ExactSizeIterator, + { + let vertices = vertices.into_iter(); + let indices = indices.into_iter(); + let mut buffers = VertexBuffers::with_capacity(vertices.len(), indices.len()); + buffers.vertices.extend(vertices); + buffers.indices.extend(indices); + Self { + buffer: buffers, + usable_indices, + } + } +} + +impl From> for OverAlignedVertexBuffer { + fn from(mut buffer: VertexBuffers) -> Self { + let usable_indices = buffer.indices.len() as u32; + buffer.align_vertices(); + buffer.align_indices(); + Self { + buffer, + usable_indices, + } + } +} + +trait Align { + fn align_vertices(&mut self); + fn align_indices(&mut self); +} + +impl Align for VertexBuffers { + fn align_vertices(&mut self) { + let align = wgpu::COPY_BUFFER_ALIGNMENT; + let stride = std::mem::size_of::() as wgpu::BufferAddress; + let unpadded_bytes = self.vertices.len() as wgpu::BufferAddress * stride; + let padding_bytes = (align - unpadded_bytes % align) % align; + + if padding_bytes != 0 { + panic!( + "vertices are always aligned to wgpu::COPY_BUFFER_ALIGNMENT \ + because GpuVertexUniform is aligned" + ) + } + } + + fn align_indices(&mut self) { + let align = wgpu::COPY_BUFFER_ALIGNMENT; + let stride = std::mem::size_of::() as wgpu::BufferAddress; + let unpadded_bytes = self.indices.len() as wgpu::BufferAddress * stride; + let padding_bytes = (align - unpadded_bytes % align) % align; + let overpad = (padding_bytes + stride - 1) / stride; // Divide by stride but round up + + for _ in 0..overpad { + self.indices.push(I::zeroed()); + } + } +} + + type GeoResult = geozero::error::Result; /// Build tessellations with vectors. @@ -33,7 +139,7 @@ pub struct ZeroTessellator } impl + MaxIndex> Default - for ZeroTessellator +for ZeroTessellator { fn default() -> Self { Self { @@ -88,7 +194,7 @@ impl + MaxIndex> ZeroTesse } impl + MaxIndex> GeomProcessor - for ZeroTessellator +for ZeroTessellator { fn xy(&mut self, x: f64, y: f64, _idx: usize) -> GeoResult<()> { // log::info!("xy"); @@ -186,12 +292,11 @@ impl + MaxIndex> GeomProce } impl + MaxIndex> PropertyProcessor - for ZeroTessellator -{ -} +for ZeroTessellator +{} impl + MaxIndex> FeatureProcessor - for ZeroTessellator +for ZeroTessellator { fn feature_end(&mut self, _idx: u64) -> geozero::error::Result<()> { self.update_feature_indices(); diff --git a/maplibre/src/vector/transferables.rs b/maplibre/src/vector/transferables.rs index 711845ffa..9f4d20f17 100644 --- a/maplibre/src/vector/transferables.rs +++ b/maplibre/src/vector/transferables.rs @@ -9,7 +9,7 @@ use crate::{ geometry_index::TileIndex, }, render::ShaderVertex, - tessellation::{IndexDataType, OverAlignedVertexBuffer}, + vector::tessellation::{IndexDataType, OverAlignedVertexBuffer}, vector::{AvailableVectorLayerData, MissingVectorLayerData}, }; From 3f6ad5b653be4b1975302302b55d4a2f1814e69d Mon Sep 17 00:00:00 2001 From: Max Ammann Date: Mon, 29 Jul 2024 23:08:40 +0200 Subject: [PATCH 05/78] Add basic SDF rendering --- data/0-255.pbf | Bin 0 -> 75579 bytes maplibre/Cargo.toml | 4 + maplibre/build.rs | 22 ++ maplibre/proto/glyphs.proto | 34 +++ maplibre/src/debug/resource_system.rs | 1 + maplibre/src/headless/map.rs | 2 +- maplibre/src/raster/resource_system.rs | 1 + maplibre/src/render/eventually.rs | 11 +- maplibre/src/render/resource/tile_pipeline.rs | 22 ++ maplibre/src/render/shaders/mod.rs | 125 ++++++++++ maplibre/src/render/shaders/sdf.fragment.wgsl | 76 ++++++ maplibre/src/render/shaders/sdf.vertex.wgsl | 36 +++ maplibre/src/render/shaders/tile.vertex.wgsl | 1 + .../src/render/systems/resource_system.rs | 1 + maplibre/src/style/layer.rs | 85 ++++++- maplibre/src/style/mod.rs | 222 ++++++++++++++++- maplibre/src/style/raster.rs | 54 ---- maplibre/src/style/style.rs | 208 ---------------- maplibre/src/vector/mod.rs | 40 ++- maplibre/src/vector/populate_world_system.rs | 16 +- maplibre/src/vector/process_vector.rs | 119 ++++++--- maplibre/src/vector/queue_system.rs | 20 ++ maplibre/src/vector/render_commands.rs | 104 ++++++++ maplibre/src/vector/request_system.rs | 24 +- maplibre/src/vector/resource/glyph_texture.rs | 39 +++ maplibre/src/vector/resource/mod.rs | 2 + maplibre/src/vector/resource_system.rs | 90 +++++++ maplibre/src/vector/tessellation.rs | 230 ++++++++++++++++++ maplibre/src/vector/text.rs | 188 ++++++++++++++ maplibre/src/vector/transferables.rs | 95 +++++++- maplibre/src/vector/upload_system.rs | 99 +++++++- 31 files changed, 1633 insertions(+), 338 deletions(-) create mode 100644 data/0-255.pbf create mode 100644 maplibre/proto/glyphs.proto create mode 100644 maplibre/src/render/shaders/sdf.fragment.wgsl create mode 100644 maplibre/src/render/shaders/sdf.vertex.wgsl delete mode 100644 maplibre/src/style/raster.rs delete mode 100644 maplibre/src/style/style.rs create mode 100644 maplibre/src/vector/resource/glyph_texture.rs create mode 100644 maplibre/src/vector/text.rs diff --git a/data/0-255.pbf b/data/0-255.pbf new file mode 100644 index 0000000000000000000000000000000000000000..e263928fb12ed678c6e27003fae6218d3837c3b8 GIT binary patch literal 75579 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

1YOCv7_!BGhg33f+ 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 ahj7Lh&L3fz@;A*NMtX1NkKfMx@jn5?XjOFp literal 0 HcmV?d00001 diff --git a/maplibre/Cargo.toml b/maplibre/Cargo.toml index 6c1125617..54a405715 100644 --- a/maplibre/Cargo.toml +++ b/maplibre/Cargo.toml @@ -88,5 +88,9 @@ smallvec.workspace = true png = { workspace = true, optional = true } image = { workspace = true, optional = true } +# Text/glyphs +prost = "0.13.1" + [build-dependencies] maplibre-build-tools = { path = "../maplibre-build-tools", version = "0.1.0" } +prost-build = "0.13.1" diff --git a/maplibre/build.rs b/maplibre/build.rs index 831fd0fd2..5e4208470 100644 --- a/maplibre/build.rs +++ b/maplibre/build.rs @@ -3,6 +3,8 @@ //! This script is built and executed just before building the package. //! It will validate the WGSL (WebGPU Shading Language) shaders and embed static files. +use std::fs; +use std::path::PathBuf; use maplibre_build_tools::wgsl::validate_project_wgsl; #[cfg(feature = "embed-static-tiles")] @@ -50,9 +52,29 @@ fn embed_tiles_statically() { } } +fn generate_protobuf() { + let proto_paths = fs::read_dir("./proto") + .unwrap() + .filter_map(|entry| { + let entry = entry.ok()?; + println!( + "cargo:rerun-if-changed={}", + entry.path().display().to_string() + ); + Some(entry.path()) + }) + .collect::>(); + + + prost_build::compile_protos(&proto_paths, &[PathBuf::from("./proto/")]).unwrap(); +} + + fn main() { validate_project_wgsl(); #[cfg(feature = "embed-static-tiles")] embed_tiles_statically(); + + generate_protobuf(); } diff --git a/maplibre/proto/glyphs.proto b/maplibre/proto/glyphs.proto new file mode 100644 index 000000000..47bca66cd --- /dev/null +++ b/maplibre/proto/glyphs.proto @@ -0,0 +1,34 @@ +// Protocol Version 1 +// Adapted from https://github.com/maplibre/maplibre-native/blob/2e3538c2fe9bcf2e3961971a0807c2d2c17a3f06/misc/proto/glyphs.proto#L4 + +package glyphs; + +option optimize_for = LITE_RUNTIME; + +// Stores a glyph with metrics and optional SDF bitmap information. +message glyph { + required uint32 id = 1; + + // A signed distance field of the glyph with a border of 3 pixels. + optional bytes bitmap = 2; + + // Glyph metrics. + required uint32 width = 3; + required uint32 height = 4; + required sint32 left = 5; + required sint32 top = 6; + required uint32 advance = 7; +} + +// Stores fontstack information and a list of faces. +message fontstack { + required string name = 1; + required string range = 2; + repeated glyph glyphs = 3; +} + +message glyphs { + repeated fontstack stacks = 1; + + extensions 16 to 8191; +} \ No newline at end of file diff --git a/maplibre/src/debug/resource_system.rs b/maplibre/src/debug/resource_system.rs index 2de8f89af..016f8a30c 100644 --- a/maplibre/src/debug/resource_system.rs +++ b/maplibre/src/debug/resource_system.rs @@ -49,6 +49,7 @@ pub fn resource_system( false, false, false, + false ) .describe_render_pipeline() .initialize(device); diff --git a/maplibre/src/headless/map.rs b/maplibre/src/headless/map.rs index b40e207dc..47c4dc46d 100644 --- a/maplibre/src/headless/map.rs +++ b/maplibre/src/headless/map.rs @@ -86,7 +86,7 @@ impl HeadlessMap { layers: layers .into_iter() .map(|layer| { - VectorLayerData::Available(AvailableVectorLayerData { + VectorLayerData::AvailableLayer(AvailableVectorLayerData { coords: layer.coords, source_layer: layer.layer_data.name, buffer: layer.buffer, diff --git a/maplibre/src/raster/resource_system.rs b/maplibre/src/raster/resource_system.rs index 6797ffba6..22189b08d 100644 --- a/maplibre/src/raster/resource_system.rs +++ b/maplibre/src/raster/resource_system.rs @@ -51,6 +51,7 @@ pub fn resource_system( false, surface.is_multisampling_supported(settings.msaa), true, + false ) .describe_render_pipeline() .initialize(device), diff --git a/maplibre/src/render/eventually.rs b/maplibre/src/render/eventually.rs index 51a5e44af..d2df00b62 100644 --- a/maplibre/src/render/eventually.rs +++ b/maplibre/src/render/eventually.rs @@ -47,9 +47,16 @@ where } impl Eventually { #[tracing::instrument(name = "initialize", skip_all)] - pub fn initialize(&mut self, f: impl FnOnce() -> T) { + pub fn initialize(&mut self, f: impl FnOnce() -> T) -> &T { if let Eventually::Uninitialized = self { - *self = Eventually::Initialized(f()); + if let Eventually::Uninitialized = self { + *self = Eventually::Initialized(f()); + } + } + + match self { + Eventually::Initialized(data) => data, + Eventually::Uninitialized => panic!("not initialized"), } } diff --git a/maplibre/src/render/resource/tile_pipeline.rs b/maplibre/src/render/resource/tile_pipeline.rs index 26c421078..e7c560b61 100644 --- a/maplibre/src/render/resource/tile_pipeline.rs +++ b/maplibre/src/render/resource/tile_pipeline.rs @@ -18,6 +18,7 @@ pub struct TilePipeline { wireframe: bool, msaa: bool, raster: bool, + glyph_rendering: bool, settings: RendererSettings, vertex_state: VertexState, @@ -36,6 +37,7 @@ impl TilePipeline { wireframe: bool, multisampling: bool, raster: bool, + glyph_rendering: bool ) -> Self { TilePipeline { name, @@ -45,6 +47,7 @@ impl TilePipeline { wireframe, msaa: multisampling, raster, + glyph_rendering, settings, vertex_state, fragment_state, @@ -95,6 +98,25 @@ impl RenderPipeline for TilePipeline { count: None, }, ]]) + } else if self.glyph_rendering { + Some(vec![vec![ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ]]) } else { None }, diff --git a/maplibre/src/render/shaders/mod.rs b/maplibre/src/render/shaders/mod.rs index b9db2a654..701d21269 100644 --- a/maplibre/src/render/shaders/mod.rs +++ b/maplibre/src/render/shaders/mod.rs @@ -388,3 +388,128 @@ impl Shader for RasterTileShader { } } } + +#[repr(C)] +#[derive(Copy, Clone, Pod, Zeroable)] +pub struct SymbolVertex { + // 4 bytes * 3 = 12 bytes + pub position: [f32; 3], + // 4 bytes * 3 = 12 bytes + pub text_anchor: [f32; 3], + // 4 bytes * 2 = 8 bytes + pub tex_coords: [f32; 2], + // 1 byte * 4 = 4 bytes + pub color: [u8; 4], + // 1 byte + pub is_glyph: u32, +} + +pub struct SymbolTileShader { + pub format: wgpu::TextureFormat, +} + +impl Shader for SymbolTileShader { + fn describe_vertex(&self) -> VertexState { + VertexState { + source: include_str!("sdf.vertex.wgsl"), + entry_point: "main", + buffers: vec![ + // vertex data + VertexBufferLayout { + array_stride: std::mem::size_of::() as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: vec![ + // position + wgpu::VertexAttribute { + offset: 0, + format: wgpu::VertexFormat::Float32x3, + shader_location: 0, + }, + // text_anchor + wgpu::VertexAttribute { + offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x3, + }, + // tex coords + wgpu::VertexAttribute { + offset: (std::mem::size_of::<[f32; 3]>() * 2) as wgpu::BufferAddress, + shader_location: 11, + format: wgpu::VertexFormat::Float32x2, + }, + ], + }, + // tile metadata + VertexBufferLayout { + array_stride: std::mem::size_of::() as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: vec![ + // translate + wgpu::VertexAttribute { + offset: 0, + format: wgpu::VertexFormat::Float32x4, + shader_location: 4, + }, + wgpu::VertexAttribute { + offset: 1 * wgpu::VertexFormat::Float32x4.size(), + format: wgpu::VertexFormat::Float32x4, + shader_location: 5, + }, + wgpu::VertexAttribute { + offset: 2 * wgpu::VertexFormat::Float32x4.size(), + format: wgpu::VertexFormat::Float32x4, + shader_location: 6, + }, + wgpu::VertexAttribute { + offset: 3 * wgpu::VertexFormat::Float32x4.size(), + format: wgpu::VertexFormat::Float32x4, + shader_location: 7, + }, + // zoom_factor + wgpu::VertexAttribute { + offset: 4 * wgpu::VertexFormat::Float32x4.size(), + format: wgpu::VertexFormat::Float32, + shader_location: 9, + }, + ], + }, + // layer metadata + VertexBufferLayout { + array_stride: std::mem::size_of::() as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: vec![ + // z_index + wgpu::VertexAttribute { + offset: 0, + format: wgpu::VertexFormat::Float32, + shader_location: 10, + }, + ], + }, + ], + } + } + + fn describe_fragment(&self) -> FragmentState { + FragmentState { + source: include_str!("sdf.fragment.wgsl"), + entry_point: "main", + targets: vec![Some(wgpu::ColorTargetState { + format: self.format, + write_mask: wgpu::ColorWrites::ALL, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::Zero, + operation: wgpu::BlendOperation::Add, + }, + }), + })], + } + } +} diff --git a/maplibre/src/render/shaders/sdf.fragment.wgsl b/maplibre/src/render/shaders/sdf.fragment.wgsl new file mode 100644 index 000000000..a17511a62 --- /dev/null +++ b/maplibre/src/render/shaders/sdf.fragment.wgsl @@ -0,0 +1,76 @@ +struct VertexOutput { + @location(0) is_glyph: i32, + @location(1) tex_coords: vec2, + @location(2) color: vec4, + @builtin(position) position: vec4, +}; + +struct Output { + @location(0) out_color: vec4, +}; + +@group(0) @binding(0) +var t_glyphs: texture_2d; +@group(0) @binding(1) +var s_glyphs: sampler; + +// Note: Ensure uniform control flow! +// https://www.khronos.org/opengl/wiki/Sampler_(GLSL)#Non-uniform_flow_control +@fragment +fn main(in: VertexOutput) -> Output { + let buffer_width: f32 = 0.25; + let buffer_center_outline: f32 = 0.8; + + // At which offset is the outline of the SDF? + let outline_center_offset: f32 = -0.25; + + // shift outline by `outline_width` to the ouside + let buffer_center: f32 = buffer_center_outline + outline_center_offset; + + let outline_color = vec3(0.0, 0.0, 0.0); + + // 0 => border, < 0 => inside, > 0 => outside + let dist = textureSample(t_glyphs, s_glyphs, in.tex_coords).r; + + let alpha: f32 = smoothstep(buffer_center - buffer_width / 2.0, buffer_center + buffer_width / 2.0, dist); + let border: f32 = smoothstep(buffer_center_outline - buffer_width / 2.0, buffer_center_outline + buffer_width / 2.0, dist); + + let color_rgb = mix(outline_color.rgb, in.color.rgb, border); + + // "Another Good Trick" from https://www.sjbaker.org/steve/omniv/alpha_sorting.html + // Using discard is an alternative for GL_ALPHA_TEST. + // https://stackoverflow.com/questions/53024693/opengl-is-discard-the-only-replacement-for-deprecated-gl-alpha-test + // Alternative is to disable the depth buffer for the RenderPass using sdf.fragment.wgsl + if (alpha == 0.0) { + discard; + } + + return Output(vec4(color_rgb, in.color.a * alpha)); + //return Output(vec4(vec3(0.0, 0.0, 0.0), 1.0)); +} + + +// MapLibre SDF shader: +/* + let SDF_PX = 8.0; + let device_pixel_ratio = 1.0; + let EDGE_GAMMA = 0.105 / device_pixel_ratio; + + let size = 6.0; // TODO + let fontScale = size / 24.0; // TODO Why / 24? + let halo_width = 0.5; // TODO + let halo_blur = 0.5; // TODO + let halo_color = vec4(1.0, 0.0, 0.0, 1.0); + + var color = in.color; + var gamma_scale = 1.0; + var gamma = EDGE_GAMMA / (fontScale * gamma_scale); + var buff = (256.0 - 64.0) / 256.0; + + let is_halo = false; + if (is_halo) { + color = halo_color; + gamma = (halo_blur * 1.19 / SDF_PX + EDGE_GAMMA) / (fontScale * gamma_scale); + buff = (6.0 - halo_width / fontScale) / SDF_PX; + } +*/ \ No newline at end of file diff --git a/maplibre/src/render/shaders/sdf.vertex.wgsl b/maplibre/src/render/shaders/sdf.vertex.wgsl new file mode 100644 index 000000000..0514506dd --- /dev/null +++ b/maplibre/src/render/shaders/sdf.vertex.wgsl @@ -0,0 +1,36 @@ + +struct VertexOutput { + @location(0) is_glyph: i32, + @location(1) tex_coords: vec2, + @location(2) color: vec4, + @builtin(position) position: vec4, +}; + +@vertex +fn main( + @location(0) position: vec3, + @location(1) text_anchor: vec3, + @location(4) translate1: vec4, + @location(5) translate2: vec4, + @location(6) translate3: vec4, + @location(7) translate4: vec4, + @location(9) zoom_factor: f32, + @location(10) z_index: f32, + @location(11) tex_coords: vec2, + @builtin(instance_index) instance_idx: u32 // instance_index is used when we have multiple instances of the same "object" +) -> VertexOutput { + let font_scale = 5.0; + + let scaling: mat3x3 = mat3x3( + vec3(zoom_factor * font_scale, 0.0, 0.0), + vec3(0.0, zoom_factor * font_scale, 0.0), + vec3(0.0, 0.0, 1.0) + ); + + var final_position = mat4x4(translate1, translate2, translate3, translate4) * vec4((scaling * (position - text_anchor) + text_anchor), 1.0); + final_position.z = z_index; + + let white = vec4(1.0, 1.0, 1.0, 1.0); + let black = vec4(0.0, 0.0, 0.0, 1.0); + return VertexOutput(1, tex_coords, white, final_position); +} \ No newline at end of file diff --git a/maplibre/src/render/shaders/tile.vertex.wgsl b/maplibre/src/render/shaders/tile.vertex.wgsl index 749cddad6..04eeb32a3 100644 --- a/maplibre/src/render/shaders/tile.vertex.wgsl +++ b/maplibre/src/render/shaders/tile.vertex.wgsl @@ -36,6 +36,7 @@ fn main( //} var final_position = mat4x4(translate1, translate2, translate3, translate4) * vec4(position + normal * width, z, 1.0); + final_position.z = z_index; return VertexOutput(color, final_position); } diff --git a/maplibre/src/render/systems/resource_system.rs b/maplibre/src/render/systems/resource_system.rs index 11bdee63b..4f82763b1 100644 --- a/maplibre/src/render/systems/resource_system.rs +++ b/maplibre/src/render/systems/resource_system.rs @@ -128,6 +128,7 @@ impl System for ResourceSystem { false, surface.is_multisampling_supported(settings.msaa), false, + false ) .describe_render_pipeline() .initialize(device); diff --git a/maplibre/src/style/layer.rs b/maplibre/src/style/layer.rs index 96593cc74..f0e9fb540 100644 --- a/maplibre/src/style/layer.rs +++ b/maplibre/src/style/layer.rs @@ -1,13 +1,12 @@ //! Vector tile layer drawing utilities. use std::collections::HashMap; +use std::hash::{Hash, Hasher}; use cint::{Alpha, EncodedSrgb}; use csscolorparser::Color; use serde::{Deserialize, Serialize}; -use crate::style::raster::RasterLayer; - #[derive(Serialize, Deserialize, Debug, Clone)] pub struct BackgroundPaint { #[serde(rename = "background-color")] @@ -26,12 +25,69 @@ pub struct FillPaint { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct LinePaint { - #[serde(rename = "line-color")] - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "line-color", skip_serializing_if = "Option::is_none")] pub line_color: Option, // TODO a lot } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum RasterResampling { + #[serde(rename = "linear")] + Linear, + #[serde(rename = "nearest")] + Nearest, +} + +/// Raster tile layer description +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RasterPaint { + #[serde(rename = "raster-brightness-max")] + #[serde(skip_serializing_if = "Option::is_none")] + pub raster_brightness_max: Option, + #[serde(rename = "raster-brightness-min")] + #[serde(skip_serializing_if = "Option::is_none")] + pub raster_brightness_min: Option, + #[serde(rename = "raster-contrast")] + #[serde(skip_serializing_if = "Option::is_none")] + pub raster_contrast: Option, + #[serde(rename = "raster-fade-duration")] + #[serde(skip_serializing_if = "Option::is_none")] + pub raster_fade_duration: Option, + #[serde(rename = "raster-hue-rotate")] + #[serde(skip_serializing_if = "Option::is_none")] + pub raster_hue_rotate: Option, + #[serde(rename = "raster-opacity")] + #[serde(skip_serializing_if = "Option::is_none")] + pub raster_opacity: Option, + #[serde(rename = "raster-resampling")] + #[serde(skip_serializing_if = "Option::is_none")] + pub raster_resampling: Option, + #[serde(rename = "raster-saturation")] + #[serde(skip_serializing_if = "Option::is_none")] + pub raster_saturation: Option, +} + +impl Default for RasterPaint { + fn default() -> Self { + RasterPaint { + raster_brightness_max: Some(1.0), + raster_brightness_min: Some(0.0), + raster_contrast: Some(0.0), + raster_fade_duration: Some(0), + raster_hue_rotate: Some(0.0), + raster_opacity: Some(1.0), + raster_resampling: Some(RasterResampling::Linear), + raster_saturation: Some(0.0), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SymbolPaint { + // TODO a lot +} + + /// The different types of paints. #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "type", content = "paint")] @@ -43,7 +99,9 @@ pub enum LayerPaint { #[serde(rename = "fill")] Fill(FillPaint), #[serde(rename = "raster")] - Raster(RasterLayer), + Raster(RasterPaint), + #[serde(rename = "symbol")] + Symbol(SymbolPaint), } impl LayerPaint { @@ -56,6 +114,7 @@ impl LayerPaint { LayerPaint::Line(paint) => paint.line_color.as_ref().map(|color| color.clone().into()), LayerPaint::Fill(paint) => paint.fill_color.as_ref().map(|color| color.clone().into()), LayerPaint::Raster(_) => None, + LayerPaint::Symbol(_) => None, } } } @@ -65,7 +124,7 @@ impl LayerPaint { pub struct StyleLayer { #[serde(skip)] pub index: u32, // FIXME: How is this initialized? - pub id: String, + pub id: String, // todo make sure that ids are unique. Styles with non-unique layer ids must not exist // TODO filter // TODO layout #[serde(skip_serializing_if = "Option::is_none")] @@ -83,6 +142,19 @@ pub struct StyleLayer { pub source_layer: Option, } +impl Eq for StyleLayer {} +impl PartialEq for StyleLayer { + fn eq(&self, other: &Self) -> bool { + self.id.eq(&other.id) + } +} + +impl Hash for StyleLayer { + fn hash(&self, state: &mut H) { + self.id.hash(state) + } +} + impl Default for StyleLayer { fn default() -> Self { Self { @@ -97,3 +169,4 @@ impl Default for StyleLayer { } } } + diff --git a/maplibre/src/style/mod.rs b/maplibre/src/style/mod.rs index 7528e0fad..e373cafa4 100644 --- a/maplibre/src/style/mod.rs +++ b/maplibre/src/style/mod.rs @@ -1,9 +1,225 @@ //! Vector tile format styling. pub use cint::*; -pub use style::*; +use std::{collections::HashMap, str::FromStr}; + +use csscolorparser::Color; +use serde::{Deserialize, Serialize}; + +use crate::style::{ + layer::{FillPaint, LayerPaint, LinePaint, StyleLayer}, + layer::RasterPaint, + source::Source, +}; +use crate::style::layer::SymbolPaint; pub mod layer; -pub mod raster; pub mod source; -mod style; + + +/// Stores the style for a multi-layered map. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Style { + pub version: u16, + pub name: String, + pub metadata: HashMap, + pub sources: HashMap, + pub layers: Vec, + pub center: Option<[f64; 2]>, // TODO: Use LatLon type here + pub zoom: Option, + pub pitch: Option, +} + +impl Default for Style { + fn default() -> Self { + Style { + version: 8, + name: "Default Style".to_string(), + metadata: Default::default(), + sources: Default::default(), + center: Some([50.85045, 4.34878]), + pitch: Some(0.0), + zoom: Some(13.0), + layers: vec![ + StyleLayer { + index: 0, + id: "park".to_string(), + maxzoom: None, + minzoom: None, + metadata: None, + paint: Some(LayerPaint::Fill(FillPaint { + fill_color: Some(Color::from_str("#c8facc").unwrap()), + })), + source: None, + source_layer: Some("park".to_string()), + }, + StyleLayer { + index: 1, + id: "landuse".to_string(), + maxzoom: None, + minzoom: None, + metadata: None, + paint: Some(LayerPaint::Fill(FillPaint { + fill_color: Some(Color::from_str("#e0dfdf").unwrap()), + })), + source: None, + source_layer: Some("landuse".to_string()), + }, + StyleLayer { + index: 2, + id: "landcover".to_string(), + maxzoom: None, + minzoom: None, + metadata: None, + paint: Some(LayerPaint::Fill(FillPaint { + fill_color: Some(Color::from_str("#aedfa3").unwrap()), + })), + source: None, + source_layer: Some("landcover".to_string()), + }, + StyleLayer { + index: 3, + id: "transportation".to_string(), + maxzoom: None, + minzoom: None, + metadata: None, + paint: Some(LayerPaint::Line(LinePaint { + line_color: Some(Color::from_str("#ffffff").unwrap()), + })), + source: None, + source_layer: Some("transportation".to_string()), + }, + StyleLayer { + index: 4, + id: "building".to_string(), + maxzoom: None, + minzoom: None, + metadata: None, + paint: Some(LayerPaint::Fill(FillPaint { + fill_color: Some(Color::from_str("#d9d0c9").unwrap()), + })), + source: None, + source_layer: Some("building".to_string()), + }, + StyleLayer { + index: 4, + id: "water".to_string(), + maxzoom: None, + minzoom: None, + metadata: None, + paint: Some(LayerPaint::Fill(FillPaint { + fill_color: Some(Color::from_str("#aad3df").unwrap()), + })), + source: None, + source_layer: Some("water".to_string()), + }, + StyleLayer { + index: 6, + id: "waterway".to_string(), + maxzoom: None, + minzoom: None, + metadata: None, + paint: Some(LayerPaint::Fill(FillPaint { + fill_color: Some(Color::from_str("#aad3df").unwrap()), + })), + source: None, + source_layer: Some("waterway".to_string()), + }, + StyleLayer { + index: 7, + id: "boundary".to_string(), + maxzoom: None, + minzoom: None, + metadata: None, + paint: Some(LayerPaint::Line(LinePaint { + line_color: Some(Color::from_str("black").unwrap()), + })), + source: None, + source_layer: Some("boundary".to_string()), + }, + StyleLayer { + index: 8, + id: "raster".to_string(), + maxzoom: None, + minzoom: None, + metadata: None, + paint: Some(LayerPaint::Raster(RasterPaint::default())), + source: None, + source_layer: Some("raster".to_string()), + }, + StyleLayer { + index: 9, + id: "text".to_string(), + maxzoom: None, + minzoom: None, + metadata: None, + paint: Some(LayerPaint::Symbol(SymbolPaint { + })), + source: None, + source_layer: Some("transportation_name".to_string()), + }, + ], + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_reading() { + // language=JSON + let style_json_str = r##" + { + "version": 8, + "name": "Test Style", + "metadata": {}, + "sources": { + "openmaptiles": { + "type": "vector", + "url": "https://maps.tuerantuer.org/europe_germany/tiles.json" + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": {"background-color": "rgb(239,239,239)"} + }, + { + "id": "transportation", + "type": "line", + "source": "openmaptiles", + "source-layer": "transportation", + "paint": { + "line-color": "#3D3D3D" + } + }, + { + "id": "boundary", + "type": "line", + "source": "openmaptiles", + "source-layer": "boundary", + "paint": { + "line-color": "#3D3D3D" + } + }, + { + "id": "building", + "minzoom": 14, + "maxzoom": 15, + "type": "fill", + "source": "openmaptiles", + "source-layer": "building", + "paint": { + "line-color": "#3D3D3D" + } + } + ] + } + "##; + + let _style: Style = serde_json::from_str(style_json_str).unwrap(); + } +} diff --git a/maplibre/src/style/raster.rs b/maplibre/src/style/raster.rs deleted file mode 100644 index d615550d5..000000000 --- a/maplibre/src/style/raster.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Raster tile layer description - -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum RasterResampling { - #[serde(rename = "linear")] - Linear, - #[serde(rename = "nearest")] - Nearest, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct RasterLayer { - #[serde(rename = "raster-brightness-max")] - #[serde(skip_serializing_if = "Option::is_none")] - pub raster_brightness_max: Option, - #[serde(rename = "raster-brightness-min")] - #[serde(skip_serializing_if = "Option::is_none")] - pub raster_brightness_min: Option, - #[serde(rename = "raster-contrast")] - #[serde(skip_serializing_if = "Option::is_none")] - pub raster_contrast: Option, - #[serde(rename = "raster-fade-duration")] - #[serde(skip_serializing_if = "Option::is_none")] - pub raster_fade_duration: Option, - #[serde(rename = "raster-hue-rotate")] - #[serde(skip_serializing_if = "Option::is_none")] - pub raster_hue_rotate: Option, - #[serde(rename = "raster-opacity")] - #[serde(skip_serializing_if = "Option::is_none")] - pub raster_opacity: Option, - #[serde(rename = "raster-resampling")] - #[serde(skip_serializing_if = "Option::is_none")] - pub raster_resampling: Option, - #[serde(rename = "raster-saturation")] - #[serde(skip_serializing_if = "Option::is_none")] - pub raster_saturation: Option, -} - -impl Default for RasterLayer { - fn default() -> Self { - RasterLayer { - raster_brightness_max: Some(1.0), - raster_brightness_min: Some(0.0), - raster_contrast: Some(0.0), - raster_fade_duration: Some(0), - raster_hue_rotate: Some(0.0), - raster_opacity: Some(1.0), - raster_resampling: Some(RasterResampling::Linear), - raster_saturation: Some(0.0), - } - } -} diff --git a/maplibre/src/style/style.rs b/maplibre/src/style/style.rs deleted file mode 100644 index 104c716a6..000000000 --- a/maplibre/src/style/style.rs +++ /dev/null @@ -1,208 +0,0 @@ -//! Default vector tile styles configuration. - -use std::{collections::HashMap, str::FromStr}; - -use csscolorparser::Color; -use serde::{Deserialize, Serialize}; - -use crate::style::{ - layer::{FillPaint, LayerPaint, LinePaint, StyleLayer}, - raster::RasterLayer, - source::Source, -}; - -/// Stores the style for a multi-layered map. -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Style { - pub version: u16, - pub name: String, - pub metadata: HashMap, - pub sources: HashMap, - pub layers: Vec, - pub center: Option<[f64; 2]>, // TODO: Use LatLon type here - pub zoom: Option, - pub pitch: Option, -} - -impl Default for Style { - fn default() -> Self { - Style { - version: 8, - name: "Default Style".to_string(), - metadata: Default::default(), - sources: Default::default(), - center: Some([50.85045, 4.34878]), - pitch: Some(0.0), - zoom: Some(13.0), - layers: vec![ - StyleLayer { - index: 0, - id: "park".to_string(), - maxzoom: None, - minzoom: None, - metadata: None, - paint: Some(LayerPaint::Fill(FillPaint { - fill_color: Some(Color::from_str("#c8facc").unwrap()), - })), - source: None, - source_layer: Some("park".to_string()), - }, - StyleLayer { - index: 1, - id: "landuse".to_string(), - maxzoom: None, - minzoom: None, - metadata: None, - paint: Some(LayerPaint::Fill(FillPaint { - fill_color: Some(Color::from_str("#e0dfdf").unwrap()), - })), - source: None, - source_layer: Some("landuse".to_string()), - }, - StyleLayer { - index: 2, - id: "landcover".to_string(), - maxzoom: None, - minzoom: None, - metadata: None, - paint: Some(LayerPaint::Fill(FillPaint { - fill_color: Some(Color::from_str("#aedfa3").unwrap()), - })), - source: None, - source_layer: Some("landcover".to_string()), - }, - StyleLayer { - index: 3, - id: "transportation".to_string(), - maxzoom: None, - minzoom: None, - metadata: None, - paint: Some(LayerPaint::Line(LinePaint { - line_color: Some(Color::from_str("#ffffff").unwrap()), - })), - source: None, - source_layer: Some("transportation".to_string()), - }, - StyleLayer { - index: 4, - id: "building".to_string(), - maxzoom: None, - minzoom: None, - metadata: None, - paint: Some(LayerPaint::Fill(FillPaint { - fill_color: Some(Color::from_str("#d9d0c9").unwrap()), - })), - source: None, - source_layer: Some("building".to_string()), - }, - StyleLayer { - index: 4, - id: "water".to_string(), - maxzoom: None, - minzoom: None, - metadata: None, - paint: Some(LayerPaint::Fill(FillPaint { - fill_color: Some(Color::from_str("#aad3df").unwrap()), - })), - source: None, - source_layer: Some("water".to_string()), - }, - StyleLayer { - index: 6, - id: "waterway".to_string(), - maxzoom: None, - minzoom: None, - metadata: None, - paint: Some(LayerPaint::Fill(FillPaint { - fill_color: Some(Color::from_str("#aad3df").unwrap()), - })), - source: None, - source_layer: Some("waterway".to_string()), - }, - StyleLayer { - index: 7, - id: "boundary".to_string(), - maxzoom: None, - minzoom: None, - metadata: None, - paint: Some(LayerPaint::Line(LinePaint { - line_color: Some(Color::from_str("black").unwrap()), - })), - source: None, - source_layer: Some("boundary".to_string()), - }, - StyleLayer { - index: 8, - id: "raster".to_string(), - maxzoom: None, - minzoom: None, - metadata: None, - paint: Some(LayerPaint::Raster(RasterLayer::default())), - source: None, - source_layer: Some("raster".to_string()), - }, - ], - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_reading() { - // language=JSON - let style_json_str = r##" - { - "version": 8, - "name": "Test Style", - "metadata": {}, - "sources": { - "openmaptiles": { - "type": "vector", - "url": "https://maps.tuerantuer.org/europe_germany/tiles.json" - } - }, - "layers": [ - { - "id": "background", - "type": "background", - "paint": {"background-color": "rgb(239,239,239)"} - }, - { - "id": "transportation", - "type": "line", - "source": "openmaptiles", - "source-layer": "transportation", - "paint": { - "line-color": "#3D3D3D" - } - }, - { - "id": "boundary", - "type": "line", - "source": "openmaptiles", - "source-layer": "boundary", - "paint": { - "line-color": "#3D3D3D" - } - }, - { - "id": "building", - "minzoom": 14, - "maxzoom": 15, - "type": "fill", - "source": "openmaptiles", - "source-layer": "building", - "paint": { - "line-color": "#3D3D3D" - } - } - ] - } - "##; - - let _style: Style = serde_json::from_str(style_json_str).unwrap(); - } -} diff --git a/maplibre/src/vector/mod.rs b/maplibre/src/vector/mod.rs index 4f6925547..7be1ade8d 100644 --- a/maplibre/src/vector/mod.rs +++ b/maplibre/src/vector/mod.rs @@ -33,6 +33,7 @@ mod upload_system; // Public due to bechmarks pub mod tessellation; +mod text; pub use process_vector::*; pub use transferables::{ @@ -41,6 +42,8 @@ pub use transferables::{ }; use crate::render::graph::RenderGraph; +use crate::render::shaders::SymbolVertex; +use crate::vector::resource::GlyphTexture; struct VectorPipeline(wgpu::RenderPipeline); impl Deref for VectorPipeline { @@ -60,6 +63,25 @@ pub type VectorBufferPool = BufferPool< ShaderFeatureStyle, >; +struct SymbolPipeline(wgpu::RenderPipeline); + +impl Deref for SymbolPipeline { + type Target = wgpu::RenderPipeline; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub type SymbolBufferPool = BufferPool< + wgpu::Queue, + wgpu::Buffer, + SymbolVertex, + IndexDataType, + ShaderLayerMetadata, + ShaderFeatureStyle, +>; + pub struct VectorPlugin(PhantomData); impl Default for VectorPlugin { @@ -96,6 +118,12 @@ impl Plugin for VectorPlugin { resources.insert(Eventually::::Uninitialized); resources.insert(Eventually::::Uninitialized); + + resources.insert(Eventually::::Uninitialized); + resources.insert(Eventually::::Uninitialized); + resources.insert(Eventually::::Uninitialized); + resources.insert(Eventually::<(wgpu::Texture, wgpu::Sampler)>::Uninitialized); + resources .get_or_init_mut::() .add_resource_query::<&Eventually>() @@ -124,13 +152,23 @@ pub struct AvailableVectorLayerData { pub feature_indices: Vec, } +pub struct AvailableSymbolVectorLayerData { + pub coords: WorldTileCoords, + pub source_layer: String, + pub buffer: OverAlignedVertexBuffer, + /// Holds for each feature the count of indices. + pub feature_indices: Vec, +} + + pub struct MissingVectorLayerData { pub coords: WorldTileCoords, pub source_layer: String, } pub enum VectorLayerData { - Available(AvailableVectorLayerData), + AvailableLayer(AvailableVectorLayerData), + AvailableSymbolLayer(AvailableSymbolVectorLayerData), Missing(MissingVectorLayerData), } diff --git a/maplibre/src/vector/populate_world_system.rs b/maplibre/src/vector/populate_world_system.rs index 29a1d569a..26e25d26f 100644 --- a/maplibre/src/vector/populate_world_system.rs +++ b/maplibre/src/vector/populate_world_system.rs @@ -33,6 +33,7 @@ impl System for PopulateWorldSystem System for PopulateWorldSystem(); + + let Some(component) = world + .tiles + .query_mut::<&mut VectorLayersDataComponent>(message.coords()) + else { + continue; + }; + + component + .layers + .push(VectorLayerData::AvailableSymbolLayer(message.to_layer())); } else if message.has_tag(T::LayerIndexed::message_tag()) { let message = message.into_transferable::(); world diff --git a/maplibre/src/vector/process_vector.rs b/maplibre/src/vector/process_vector.rs index aa74ecdc1..0a4d3c240 100644 --- a/maplibre/src/vector/process_vector.rs +++ b/maplibre/src/vector/process_vector.rs @@ -18,6 +18,10 @@ use crate::{ LayerIndexed, LayerMissing, LayerTessellated, TileTessellated, VectorTransferables, }, }; +use crate::render::shaders::SymbolVertex; +use crate::style::layer::{LayerPaint, StyleLayer}; +use crate::vector::tessellation::TextTessellator; +use crate::vector::transferables::SymbolLayerTessellated; #[derive(Error, Debug)] pub enum ProcessVectorError { @@ -32,7 +36,7 @@ pub enum ProcessVectorError { /// A request for a tile at the given coordinates and in the given layers. pub struct VectorTileRequest { pub coords: WorldTileCoords, - pub layers: HashSet, + pub layers: HashSet, } pub fn process_vector_tile( @@ -40,54 +44,84 @@ pub fn process_vector_tile( tile_request: VectorTileRequest, context: &mut ProcessVectorContext, ) -> Result<(), ProcessVectorError> { - // Decode - let mut tile = geozero::mvt::Tile::decode(data) .map_err(|e| ProcessVectorError::Decoding(e.to_string().into()))?; - // Available - + // Report available layers let coords = &tile_request.coords; - for layer in &mut tile.layers { - let cloned_layer = layer.clone(); - let layer_name: &str = &cloned_layer.name; - if !tile_request.layers.contains(layer_name) { - continue; - } - - let mut tessellator = ZeroTessellator::::default(); - if let Err(e) = layer.process(&mut tessellator) { - context.layer_missing(coords, layer_name)?; - - tracing::error!("layer {layer_name} at {coords} tesselation failed {e:?}"); + for style_layer in &tile_request.layers { + let id = &style_layer.id; + if let (Some(paint), Some(source_layer)) = (&style_layer.paint, &style_layer.source_layer) { + + if let Some(layer) = tile.layers.iter_mut().find(|layer| &layer.name == source_layer) { + let original_layer = layer.clone(); + + match paint { + LayerPaint::Line(_) | LayerPaint::Fill(_) => { + let mut tessellator = ZeroTessellator::::default(); + + if let Err(e) = layer.process(&mut tessellator) { + context.layer_missing(coords, &source_layer)?; + + tracing::error!("tesselation for layer source {source_layer} at {coords} failed {e:?}"); + } else { + context.layer_tesselation_finished( + coords, + tessellator.buffer.into(), + tessellator.feature_indices, + original_layer, + )?; + } + } + LayerPaint::Symbol(_) => { + + let mut tessellator = TextTessellator::::default(); + + if let Err(e) = layer.process(&mut tessellator) { + context.layer_missing(coords, &source_layer)?; + + tracing::error!("tesselation for layer source {source_layer} at {coords} failed {e:?}"); + } else { + if tessellator.quad_buffer.indices.is_empty() { + log::error!("quad buffer empty"); + continue + } + context.symbol_layer_tesselation_finished( + coords, + tessellator.quad_buffer.into(), + tessellator.feature_indices, + original_layer, + )?; + } + + } + _ => { + log::warn!("unhandled style layer type in {id}"); + } + } + } else { + log::warn!("layer source {source_layer} not found in vector tile"); + } } else { - context.layer_tesselation_finished( - coords, - tessellator.buffer.into(), - tessellator.feature_indices, - cloned_layer, - )?; + log::error!("vector style layer {id} misses a required attribute"); } } - // Missing - + // Report missing layers let coords = &tile_request.coords; - let available_layers: HashSet<_> = tile .layers .iter() .map(|layer| layer.name.clone()) .collect::>(); - for missing_layer in tile_request.layers.difference(&available_layers) { - context.layer_missing(coords, missing_layer)?; - tracing::info!("requested layer {missing_layer} at {coords} not found in tile"); - } - - // Indexing + // todo for missing_layer in tile_request.layers.difference(&available_layers) { + // context.layer_missing(coords, &missing_layer.id)?; + // tracing::info!("requested layer {missing_layer} at {coords} not found in tile"); + //} + // Report index for layer let mut index = IndexProcessor::new(); for layer in &mut tile.layers { @@ -96,8 +130,7 @@ pub fn process_vector_tile( context.layer_indexing_finished(&tile_request.coords, index.get_geometries())?; - // End - + // Report end tracing::info!("tile tessellated at {coords} finished"); context.tile_finished(coords)?; @@ -156,6 +189,24 @@ impl ProcessVectorContext { .map_err(|e| ProcessVectorError::SendError(e)) } + fn symbol_layer_tesselation_finished( + &mut self, + coords: &WorldTileCoords, + buffer: OverAlignedVertexBuffer, + feature_indices: Vec, + layer_data: tile::Layer, + ) -> Result<(), ProcessVectorError> { + self.context + .send(T::SymbolLayerTessellated::build_from( + *coords, + buffer, + feature_indices, + layer_data, + )) + .map_err(|e| ProcessVectorError::SendError(e)) + } + + fn layer_indexing_finished( &mut self, coords: &WorldTileCoords, diff --git a/maplibre/src/vector/queue_system.rs b/maplibre/src/vector/queue_system.rs index adfc5a308..cc1cbc7d5 100644 --- a/maplibre/src/vector/queue_system.rs +++ b/maplibre/src/vector/queue_system.rs @@ -10,6 +10,8 @@ use crate::{ tcs::tiles::Tile, vector::{render_commands::DrawVectorTiles, VectorBufferPool}, }; +use crate::vector::render_commands::DrawSymbols; +use crate::vector::SymbolBufferPool; pub fn queue_system(MapContext { world, .. }: &mut MapContext) { let Some(( @@ -17,11 +19,13 @@ pub fn queue_system(MapContext { world, .. }: &mut MapContext) { Initialized(buffer_pool), mask_phase, layer_item_phase, + Initialized(symbol_buffer_pool) )) = world.resources.query_mut::<( &mut Eventually, &mut Eventually, &mut RenderPhase, &mut RenderPhase, + &mut Eventually, )>() else { return; @@ -41,8 +45,23 @@ pub fn queue_system(MapContext { world, .. }: &mut MapContext) { source_shape: source_shape.clone(), }); + if let Some(layer_entries) = symbol_buffer_pool.index().get_layers(source_shape.coords()) { + for layer_entry in layer_entries { + // Draw tile + layer_item_phase.add(LayerItem { + draw_function: Box::new(DrawState::::new()), + index: layer_entry.style_layer.index, + style_layer: layer_entry.style_layer.id.clone(), + tile: Tile { + coords: layer_entry.coords, + }, + source_shape: source_shape.clone(), + }); + } + }; if let Some(layer_entries) = buffer_pool_index.get_layers(source_shape.coords()) { for layer_entry in layer_entries { + // continue; // Draw tile layer_item_phase.add(LayerItem { draw_function: Box::new(DrawState::::new()), @@ -55,6 +74,7 @@ pub fn queue_system(MapContext { world, .. }: &mut MapContext) { }); } }; + }); } } diff --git a/maplibre/src/vector/render_commands.rs b/maplibre/src/vector/render_commands.rs index 9a6166727..3fa36695f 100644 --- a/maplibre/src/vector/render_commands.rs +++ b/maplibre/src/vector/render_commands.rs @@ -11,6 +11,8 @@ use crate::{ tcs::world::World, vector::{VectorBufferPool, VectorPipeline}, }; +use crate::vector::resource::GlyphTexture; +use crate::vector::{SymbolBufferPool, SymbolPipeline}; pub struct SetVectorTilePipeline; impl RenderCommand

for SetVectorTilePipeline { @@ -107,3 +109,105 @@ impl RenderCommand for DrawVectorTile { } pub type DrawVectorTiles = (SetVectorTilePipeline, DrawVectorTile); + + +pub struct SetSymbolPipeline; +impl RenderCommand

for SetSymbolPipeline { + fn render<'w>( + world: &'w World, + _item: &P, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let Some((Initialized(GlyphTexture { ref bind_group, .. }), Initialized(symbol_pipeline))) = + world.resources.query::<( + &Eventually, + &Eventually, + )>() + else { + return RenderCommandResult::Failure; + }; + + pass.set_bind_group(0, bind_group, &[]); + pass.set_render_pipeline(symbol_pipeline); + RenderCommandResult::Success + } +} + +pub struct DrawSymbol; +impl RenderCommand for DrawSymbol { + fn render<'w>( + world: &'w World, + item: &LayerItem, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let Some((Initialized(symbol_buffer_pool), Initialized(tile_view_pattern))) = + world.resources.query::<( + &Eventually, + &Eventually, + )>() + else { + return RenderCommandResult::Failure; + }; + + let Some(vector_layers) = symbol_buffer_pool.index().get_layers(item.tile.coords) else { + return RenderCommandResult::Failure; + }; + + let Some(entry) = vector_layers + .iter() + .find(|entry| entry.style_layer.id == item.style_layer) + else { + return RenderCommandResult::Failure; + }; + + let source_shape = &item.source_shape; + + let tile_view_pattern_buffer = source_shape + .buffer_range() + .expect("tile_view_pattern needs to be uploaded first"); // FIXME tcs + + // Uses stencil value of requested tile and the shape of the requested tile + let reference = source_shape.coords().stencil_reference_value_3d() as u32; + + tracing::trace!( + "Drawing layer {:?} at {}", + entry.style_layer.source_layer, + entry.coords + ); + + let index_range = entry.indices_buffer_range(); + + if index_range.is_empty() { + tracing::error!("Tried to draw a vector tile without any vertices"); + return RenderCommandResult::Failure; + } + + pass.set_stencil_reference(reference); + + pass.set_index_buffer( + symbol_buffer_pool + .indices() + .slice(index_range), + INDEX_FORMAT, + ); + pass.set_vertex_buffer( + 0, + symbol_buffer_pool + .vertices() + .slice(entry.vertices_buffer_range()), + ); + pass.set_vertex_buffer(1, tile_view_pattern.buffer().slice(tile_view_pattern_buffer)); + pass.set_vertex_buffer( + 2, + symbol_buffer_pool + .metadata() + .slice(entry.layer_metadata_buffer_range()), + ); + + pass.draw_indexed(entry.indices_range(), 0, 0..1); + RenderCommandResult::Success + } +} + + +pub type DrawSymbols = (SetSymbolPipeline, DrawSymbol); \ No newline at end of file diff --git a/maplibre/src/vector/request_system.rs b/maplibre/src/vector/request_system.rs index 1c410fead..24214244c 100644 --- a/maplibre/src/vector/request_system.rs +++ b/maplibre/src/vector/request_system.rs @@ -11,7 +11,6 @@ use crate::{ }, kernel::Kernel, render::tile_view_pattern::DEFAULT_TILE_SIZE, - style::layer::LayerPaint, tcs::system::System, vector::{ process_vector::{process_vector_tile, ProcessVectorContext, VectorTileRequest}, @@ -19,6 +18,7 @@ use crate::{ VectorLayersDataComponent, }, }; +use crate::style::layer::StyleLayer; pub struct RequestSystem { kernel: Rc>, @@ -113,23 +113,13 @@ pub fn fetch_vector_apc = style - .layers - .iter() - .filter_map(|layer| { - if matches!(layer.paint, Some(LayerPaint::Fill(_))) - || matches!(layer.paint, Some(LayerPaint::Line(_))) - { - layer.source_layer.clone() - } else { - None - } - }) + let requested_layers: HashSet = style + .layers.iter().cloned() .collect(); let client = kernel.source_client(); - if !fill_layers.is_empty() { + if !style.layers.is_empty() { let context = context.clone(); let source = SourceType::Tessellate(TessellateSource::default()); match client.fetch(&coords, &source).await { @@ -141,7 +131,7 @@ pub fn fetch_vector_apc { log::error!("{e:?}"); - for to_load in &fill_layers { + for to_load in &requested_layers { context .send_back(::LayerMissing::build_from( coords, - to_load.to_string(), + to_load.id.clone(), )) .map_err(ProcedureError::Send)?; } diff --git a/maplibre/src/vector/resource/glyph_texture.rs b/maplibre/src/vector/resource/glyph_texture.rs new file mode 100644 index 000000000..51ddc200d --- /dev/null +++ b/maplibre/src/vector/resource/glyph_texture.rs @@ -0,0 +1,39 @@ +pub struct GlyphTexture { + pub bind_group: wgpu::BindGroup, +} + +impl GlyphTexture { + pub fn from_device( + device: &wgpu::Device, + texture: &wgpu::Texture, + sampler: &wgpu::Sampler, + layout: &wgpu::BindGroupLayout, + ) -> Self { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture.create_view( + &wgpu::TextureViewDescriptor { + label: Some("Glyph texture view"), + format: Some(wgpu::TextureFormat::R8Unorm), + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }, + )), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + label: Some("Glyph texture bind group"), + }); + Self { bind_group } + } +} \ No newline at end of file diff --git a/maplibre/src/vector/resource/mod.rs b/maplibre/src/vector/resource/mod.rs index 5d22b8dfa..207585d12 100644 --- a/maplibre/src/vector/resource/mod.rs +++ b/maplibre/src/vector/resource/mod.rs @@ -1,3 +1,5 @@ pub use buffer_pool::*; +pub use glyph_texture::*; mod buffer_pool; +mod glyph_texture; diff --git a/maplibre/src/vector/resource_system.rs b/maplibre/src/vector/resource_system.rs index efc4c579e..ffd91b046 100644 --- a/maplibre/src/vector/resource_system.rs +++ b/maplibre/src/vector/resource_system.rs @@ -1,4 +1,5 @@ //! Prepares GPU-owned resources by initializing them if they are uninitialized or out-of-date. +use wgpu::util::{DeviceExt, TextureDataOrder}; use crate::{ context::MapContext, render::{ @@ -10,6 +11,9 @@ use crate::{ }, vector::{resource::BufferPool, VectorBufferPool, VectorPipeline}, }; +use crate::vector::{SymbolBufferPool, SymbolPipeline}; +use crate::vector::resource::GlyphTexture; +use crate::vector::text::GlyphSet; pub fn resource_system( MapContext { @@ -17,6 +21,7 @@ pub fn resource_system( renderer: Renderer { device, + queue, resources: RenderResources { surface, .. }, settings, .. @@ -49,10 +54,95 @@ pub fn resource_system( false, surface.is_multisampling_supported(settings.msaa), false, + false ) .describe_render_pipeline() .initialize(device); VectorPipeline(pipeline) }); + + + let Some((symbol_buffer_pool, symbol_pipeline, glyph_texture_sampler, glyph_texture_bind_group)) = world.resources.query_mut::<( + &mut Eventually, + &mut Eventually, + &mut Eventually<(wgpu::Texture, wgpu::Sampler)>, + &mut Eventually, + )>() else { + return; + }; + + symbol_buffer_pool.initialize(|| BufferPool::from_device(device)); + + symbol_pipeline.initialize(|| { + let tile_shader = shaders::SymbolTileShader { + format: surface.surface_format(), + }; + + let pipeline = TilePipeline::new( + "symbol_pipeline".into(), + *settings, + tile_shader.describe_vertex(), + tile_shader.describe_fragment(), + true, + false, + true, // TODO ignore tile mask + false, + surface.is_multisampling_supported(settings.msaa), + false, + true + ) + .describe_render_pipeline() + .initialize(device); + + + let (texture, sampler) = glyph_texture_sampler.initialize(|| { + let data = std::fs::read("./data/0-255.pbf").unwrap(); + let glyphs = GlyphSet::try_from( + data.as_slice(), + ).unwrap(); + + let (width, height) = glyphs.get_texture_dimensions(); + + let texture = device.create_texture_with_data( + &queue, + &wgpu::TextureDescriptor { + label: Some("Glyph Texture"), + size: wgpu::Extent3d { + width: width as _, + height: height as _, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::R8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[wgpu::TextureFormat::R8Unorm], // TODO + }, + TextureDataOrder::LayerMajor, // TODO + glyphs.get_texture_bytes(), + ); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + // SDF rendering requires linear interpolation + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + + (texture, sampler) + }); + + glyph_texture_bind_group.initialize(|| { + GlyphTexture::from_device( + device, + texture, + sampler, + &pipeline.get_bind_group_layout(0), + ) + }); + + SymbolPipeline(pipeline) + }); } diff --git a/maplibre/src/vector/tessellation.rs b/maplibre/src/vector/tessellation.rs index 79ee502b1..08fb5ef6e 100644 --- a/maplibre/src/vector/tessellation.rs +++ b/maplibre/src/vector/tessellation.rs @@ -11,6 +11,14 @@ use lyon::{ StrokeOptions, StrokeTessellator, }, }; +use std::fs; + +use csscolorparser::Color; +use geozero::{ColumnValue}; +use lyon::{ + geom::{euclid::Point2D, Box2D}, + +}; use crate::{ render::ShaderVertex, @@ -20,6 +28,8 @@ use bytemuck::Pod; use lyon::tessellation::{ FillVertex, FillVertexConstructor, StrokeVertex, StrokeVertexConstructor, VertexBuffers, }; +use crate::render::shaders::SymbolVertex; +use crate::vector::text::{Anchor, GlyphSet, SymbolVertexBuilder}; const DEFAULT_TOLERANCE: f32 = 0.02; @@ -154,6 +164,8 @@ for ZeroTessellator } impl + MaxIndex> ZeroTessellator { + /// Stores current indices to the output. That way we know which vertices correspond to which + /// feature in the output. fn update_feature_indices(&mut self) { let next_index = self.buffer.indices.len(); let indices = (next_index - self.current_index) as u32; @@ -303,3 +315,221 @@ for ZeroTessellator Ok(()) } } + +/// Build tessellations with vectors. +pub struct TextTessellator + MaxIndex> { + glyphs: GlyphSet, + + // output + pub quad_buffer: VertexBuffers, + pub feature_indices: Vec, + + // iteration variables + current_index: usize, + current_text: Option, + current_bbox: Option>, +} + +impl + MaxIndex> Default +for TextTessellator +{ + fn default() -> Self { + let data = fs::read("./data/0-255.pbf").unwrap(); + let glyphs = GlyphSet::try_from(data.as_slice()).unwrap(); + Self { + glyphs, + quad_buffer: VertexBuffers::new(), + feature_indices: Vec::new(), + current_index: 0, + current_text: None, + current_bbox: None, + } + } +} + +impl + MaxIndex> TextTessellator { + pub fn tessellate_glyph_quads( + &mut self, + origin: [f32; 2], + label_text: &str, + color: Color, + ) -> Option> { + let mut tessellator = FillTessellator::new(); + + let mut next_origin = origin; + + let texture_dimensions = self.glyphs.get_texture_dimensions(); + let texture_dimensions = (texture_dimensions.0 as f32, texture_dimensions.1 as f32); + + // TODO: silently drops unknown characters + // TODO: handle line wrapping / line height + let mut bbox = None; + for glyph in label_text + .chars() + .filter_map(|c| self.glyphs.glyphs.get(&c)) + .collect::>() + { + let glyph_dims = glyph.buffered_dimensions(); + let width = glyph_dims.0 as f32; + let height = glyph_dims.1 as f32; + + let glyph_anchor = [ + next_origin[0] + glyph.left_bearing as f32, + next_origin[1] - glyph.top_bearing as f32, + 0., + ]; + + let glyph_rect = Box2D::new( + (glyph_anchor[0], glyph_anchor[1]).into(), + (glyph_anchor[0] + width, glyph_anchor[1] + height).into(), + ); + + bbox = bbox.map_or_else( + || Some(glyph_rect), + |bbox: Box2D<_>| Some(bbox.union(&glyph_rect)), + ); + + tessellator + .tessellate_rectangle( + &glyph_rect, + &FillOptions::default(), + &mut BuffersBuilder::new( + &mut self.quad_buffer, + SymbolVertexBuilder { + glyph_anchor, + text_anchor: [origin[0], origin[1], 0.0], + texture_dimensions, + sprite_dimensions: (width, height), + sprite_offset: ( + glyph.origin_offset().0 as f32, + glyph.origin_offset().1 as f32, + ), + color: color.to_rgba8(), // TODO: is this conversion oke? + glyph: true, // Set here to true to use SDF rendering + }, + ), + ) + .ok()?; + + next_origin[0] += glyph.advance() as f32; + } + + bbox + } +} + +impl + MaxIndex> GeomProcessor +for TextTessellator +{ + fn xy(&mut self, x: f64, y: f64, _idx: usize) -> GeoResult<()> { + let new_box = Box2D::new( + Point2D::new(x as f32, y as f32), + Point2D::new(x as f32, y as f32), + ); + if let Some(bbox) = self.current_bbox { + self.current_bbox = Some(bbox.union(&new_box)) + } else { + self.current_bbox = Some(new_box) + } + Ok(()) + } + + fn point_begin(&mut self, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn point_end(&mut self, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn multipoint_begin(&mut self, _size: usize, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn multipoint_end(&mut self, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn linestring_begin(&mut self, _tagged: bool, _size: usize, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn linestring_end(&mut self, _tagged: bool, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn multilinestring_begin(&mut self, _size: usize, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn multilinestring_end(&mut self, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn polygon_begin(&mut self, _tagged: bool, _size: usize, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn polygon_end(&mut self, _tagged: bool, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn multipolygon_begin(&mut self, _size: usize, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn multipolygon_end(&mut self, _idx: usize) -> GeoResult<()> { + Ok(()) + } +} + +impl + MaxIndex> PropertyProcessor +for TextTessellator +{ + fn property( + &mut self, + _idx: usize, + name: &str, + value: &ColumnValue, + ) -> geozero::error::Result { + if name == "name" { // TODO: Support different tags + match value { + ColumnValue::String(str) => { + self.current_text = Some(str.to_string()); + } + _ => {} + } + } + Ok(true) + } +} + +impl + MaxIndex> FeatureProcessor +for TextTessellator +{ + fn feature_end(&mut self, _idx: u64) -> geozero::error::Result<()> { + if let (Some(bbox), Some(text)) = (&self.current_bbox, self.current_text.clone()) { + let anchor = Anchor::Center; + // TODO: add more anchor possibilities; only support center right now + // TODO: document how anchor and glyph metrics work together to establish a baseline + let origin = match anchor { + Anchor::Center => bbox.center().to_array(), + _ => unimplemented!("no support for this anchor"), + }; + self.tessellate_glyph_quads( + origin, + text.as_str(), + Color::from_linear_rgba(1.0, 0., 0., 1.), + ); + + let next_index = self.quad_buffer.indices.len(); + let indices = (next_index - self.current_index) as u32; + self.feature_indices.push(indices); + self.current_index = next_index; + } + + self.current_bbox = None; + self.current_text = None; + Ok(()) + } +} diff --git a/maplibre/src/vector/text.rs b/maplibre/src/vector/text.rs new file mode 100644 index 000000000..f159f93e6 --- /dev/null +++ b/maplibre/src/vector/text.rs @@ -0,0 +1,188 @@ +use std::{collections::BTreeMap, convert::TryFrom}; + +use image::{GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma}; +use lyon::tessellation::{FillVertex, FillVertexConstructor}; +use prost::{DecodeError, Message}; +use crate::render::shaders::SymbolVertex; + +pub mod sdf_glyphs { + include!(concat!(env!("OUT_DIR"), "/glyphs.rs")); +} + +pub type UnicodePoint = char; + +#[derive(Debug)] +pub struct Glyph { + pub codepoint: UnicodePoint, + pub width: u32, + pub height: u32, + pub left_bearing: i32, + pub top_bearing: i32, + h_advance: u32, + /// x origin coordinate within the packed texture + tex_origin_x: u32, + /// y origin coordinate within the packed texture + tex_origin_y: u32, +} + +impl Glyph { + fn from_pbf(g: sdf_glyphs::Glyph, origin_x: u32, origin_y: u32) -> Self { + Self { + codepoint: char::try_from(g.id).unwrap(), + width: g.width, + height: g.height, + left_bearing: g.left, + top_bearing: g.top, + h_advance: g.advance, + tex_origin_x: origin_x, + tex_origin_y: origin_y, + } + } + + pub fn buffered_dimensions(&self) -> (u32, u32) { + (self.width + 3 * 2, self.height + 3 * 2) + } + pub fn origin_offset(&self) -> (u32, u32) { + (self.tex_origin_x, self.tex_origin_y) + } + pub fn advance(&self) -> u32 { + self.h_advance + } +} + +pub struct GlyphSet { + texture_bytes: Vec, + texture_dimensions: (usize, usize), + pub glyphs: BTreeMap, +} + +impl TryFrom<&[u8]> for GlyphSet { + type Error = DecodeError; + + fn try_from(value: &[u8]) -> Result { + Ok(GlyphSet::from(sdf_glyphs::Glyphs::decode(value)?)) + } +} + +impl From for GlyphSet { + fn from(pbf_glyphs: sdf_glyphs::Glyphs) -> Self { + let stacks = pbf_glyphs.stacks; + let mut texture: GrayImage = ImageBuffer::new(4096, 4096); + let mut last_position = (0, 0); + let mut max_height = 0; + + let glyphs = stacks + .into_iter() + .flat_map(|stack| { + stack + .glyphs + .into_iter() + .filter_map(|mut glyph| { + // Save an extra copy operation by taking the bits out directly. + let bitmap = glyph.bitmap.take()?; + + let glyph = Glyph::from_pbf(glyph, last_position.0, last_position.1); + + let buffered_width = glyph.width + 3 * 2; + let buffered_height = glyph.height + 3 * 2; + + let glyph_texture = ImageBuffer::, _>::from_vec( + buffered_width, + buffered_height, + bitmap, + )?; + assert_eq!(buffered_height, glyph_texture.height()); + assert_eq!(buffered_width, glyph_texture.width()); + + // TODO: wraparound on texture width overflow + texture + .copy_from(&glyph_texture, last_position.0, last_position.1) + .expect("Unable to copy glyph texture."); + + last_position.0 += glyph_texture.width(); + max_height = max_height.max(glyph_texture.height()); + + Some((glyph.codepoint, glyph)) + }) + .collect::>() + }) + .collect(); + + Self { + texture_bytes: texture + .view(0, 0, last_position.0, max_height) + .pixels() + .map(|(_x, _y, p)| p[0]) + .collect(), + texture_dimensions: (last_position.0 as _, max_height as _), + glyphs, + } + } +} + +impl GlyphSet { + pub fn get_texture_dimensions(&self) -> (usize, usize) { + self.texture_dimensions + } + + pub fn get_texture_bytes(&self) -> &[u8] { + self.texture_bytes.as_slice() + } +} + +pub struct SymbolVertexBuilder { + /// Where is the top-left anchor of the glyph box + pub glyph_anchor: [f32; 3], + /// Where is the top-left anchor of the text box + pub text_anchor: [f32; 3], + /// Size of sprite-sheet * font_scale + pub texture_dimensions: (f32, f32), + /// Size of individual glyph * font_scale + pub sprite_dimensions: (f32, f32), + /// where in the sheet is the sprite * font_scale + pub sprite_offset: (f32, f32), + pub glyph: bool, + pub color: [u8; 4], +} + +impl FillVertexConstructor for SymbolVertexBuilder { + fn new_vertex(&mut self, vertex: FillVertex) -> SymbolVertex { + let vertex_position = vertex.position(); + + let sprite_ratio_x = self.sprite_dimensions.0 / self.texture_dimensions.0; + let sprite_ratio_y = self.sprite_dimensions.1 / self.texture_dimensions.1; + + let x_offset = self.sprite_offset.0 / self.texture_dimensions.0; + let y_offset = self.sprite_offset.1 / self.texture_dimensions.1; + + let tex_coords = [ + x_offset + + ((vertex_position.x - self.glyph_anchor[0]) / self.sprite_dimensions.0) + * sprite_ratio_x, + y_offset + + ((vertex_position.y - self.glyph_anchor[1]) / self.sprite_dimensions.1) + * sprite_ratio_y, + ]; + + SymbolVertex { + position: [vertex_position.x, vertex_position.y, 0.], + text_anchor: self.text_anchor, + is_glyph: if self.glyph { 1 } else { 0 }, + color: self.color, + tex_coords, + } + } +} + +#[derive(Debug, Copy, Clone)] +pub enum Anchor { + Center, + Left, + Right, + Top, + Bottom, + TopLeft, + TopRight, + BottomLeft, + BottomRight, +} \ No newline at end of file diff --git a/maplibre/src/vector/transferables.rs b/maplibre/src/vector/transferables.rs index 9f4d20f17..94a833dff 100644 --- a/maplibre/src/vector/transferables.rs +++ b/maplibre/src/vector/transferables.rs @@ -12,13 +12,16 @@ use crate::{ vector::tessellation::{IndexDataType, OverAlignedVertexBuffer}, vector::{AvailableVectorLayerData, MissingVectorLayerData}, }; +use crate::render::shaders::SymbolVertex; +use crate::vector::AvailableSymbolVectorLayerData; #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub enum VectorMessageTag { TileTessellated = 1, LayerMissing = 2, LayerTessellated = 3, - LayerIndexed = 4, + SymbolLayerTessellated = 4, + LayerIndexed = 10, } impl MessageTag for VectorMessageTag { @@ -70,6 +73,25 @@ pub trait LayerTessellated: IntoMessage + Debug + Send { fn to_layer(self) -> AvailableVectorLayerData; } +pub trait SymbolLayerTessellated: IntoMessage + Debug + Send { + fn message_tag() -> &'static dyn MessageTag; + + fn build_from( + coords: WorldTileCoords, + buffer: OverAlignedVertexBuffer, + feature_indices: Vec, + layer_data: Layer, + ) -> Self + where + Self: Sized; + + fn coords(&self) -> WorldTileCoords; + + fn is_empty(&self) -> bool; + + fn to_layer(self) -> AvailableSymbolVectorLayerData; +} + pub trait LayerIndexed: IntoMessage + Debug + Send { fn message_tag() -> &'static dyn MessageTag; @@ -155,7 +177,7 @@ impl LayerMissing for DefaultLayerMissing { } #[derive(Clone)] -pub struct DefaultLayerTesselated { +pub struct DefaultLayerTessellated { pub coords: WorldTileCoords, pub buffer: OverAlignedVertexBuffer, /// Holds for each feature the count of indices. @@ -163,19 +185,19 @@ pub struct DefaultLayerTesselated { pub layer_data: Layer, // FIXME (perf): Introduce a better structure for this } -impl Debug for DefaultLayerTesselated { +impl Debug for DefaultLayerTessellated { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "DefaultLayerTesselated({})", self.coords) } } -impl IntoMessage for DefaultLayerTesselated { +impl IntoMessage for DefaultLayerTessellated { fn into(self) -> Message { Message::new(Self::message_tag(), Box::new(self)) } } -impl LayerTessellated for DefaultLayerTesselated { +impl LayerTessellated for DefaultLayerTessellated { fn message_tag() -> &'static dyn MessageTag { &VectorMessageTag::LayerTessellated } @@ -212,6 +234,65 @@ impl LayerTessellated for DefaultLayerTesselated { } } + +#[derive(Clone)] +pub struct DefaultSymbolLayerTessellated { + pub coords: WorldTileCoords, + pub buffer: OverAlignedVertexBuffer, + /// Holds for each feature the count of indices. + pub feature_indices: Vec, + pub layer_data: Layer, // FIXME (perf): Introduce a better structure for this +} + +impl Debug for crate::vector::transferables::DefaultSymbolLayerTessellated { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "DefaultSymbolLayerTessellated({})", self.coords) + } +} + +impl IntoMessage for crate::vector::transferables::DefaultSymbolLayerTessellated { + fn into(self) -> Message { + Message::new(Self::message_tag(), Box::new(self)) + } +} + +impl SymbolLayerTessellated for crate::vector::transferables::DefaultSymbolLayerTessellated { + fn message_tag() -> &'static dyn MessageTag { + &VectorMessageTag::SymbolLayerTessellated + } + + fn build_from( + coords: WorldTileCoords, + buffer: OverAlignedVertexBuffer, + feature_indices: Vec, + layer_data: Layer, + ) -> Self { + Self { + coords, + buffer, + feature_indices, + layer_data, + } + } + + fn coords(&self) -> WorldTileCoords { + self.coords + } + + fn is_empty(&self) -> bool { + self.buffer.usable_indices == 0 + } + + fn to_layer(self) -> AvailableSymbolVectorLayerData { + AvailableSymbolVectorLayerData { + coords: self.coords, + source_layer: self.layer_data.name, + buffer: self.buffer, + feature_indices: self.feature_indices, + } + } +} + pub struct DefaultLayerIndexed { coords: WorldTileCoords, index: TileIndex, @@ -251,6 +332,7 @@ pub trait VectorTransferables: Copy + Clone + 'static { type TileTessellated: TileTessellated; type LayerMissing: LayerMissing; type LayerTessellated: LayerTessellated; + type SymbolLayerTessellated: SymbolLayerTessellated; type LayerIndexed: LayerIndexed; } @@ -260,6 +342,7 @@ pub struct DefaultVectorTransferables; impl VectorTransferables for DefaultVectorTransferables { type TileTessellated = DefaultTileTessellated; type LayerMissing = DefaultLayerMissing; - type LayerTessellated = DefaultLayerTesselated; + type LayerTessellated = DefaultLayerTessellated; + type SymbolLayerTessellated = DefaultSymbolLayerTessellated; type LayerIndexed = DefaultLayerIndexed; } diff --git a/maplibre/src/vector/upload_system.rs b/maplibre/src/vector/upload_system.rs index 3ba3c503b..9c3efe6ec 100644 --- a/maplibre/src/vector/upload_system.rs +++ b/maplibre/src/vector/upload_system.rs @@ -17,6 +17,7 @@ use crate::{ AvailableVectorLayerData, VectorBufferPool, VectorLayerData, VectorLayersDataComponent, }, }; +use crate::vector::{AvailableSymbolVectorLayerData, SymbolBufferPool}; pub fn upload_system( MapContext { @@ -27,9 +28,9 @@ pub fn upload_system( .. }: &mut MapContext, ) { - let Some(Initialized(buffer_pool)) = world + let Some((Initialized(buffer_pool), Initialized(symbol_buffer_pool))) = world .resources - .query_mut::<&mut Eventually>() + .query_mut::<(&mut Eventually, &mut Eventually)>() else { return; }; @@ -38,7 +39,7 @@ pub fn upload_system( view_state.create_view_region(view_state.zoom().zoom_level(DEFAULT_TILE_SIZE)); if let Some(view_region) = &view_region { - upload_tesselated_layer( + upload_tessellated_layer( buffer_pool, device, queue, @@ -46,6 +47,14 @@ pub fn upload_system( style, view_region, ); + upload_symbol_layer( + symbol_buffer_pool, + device, + queue, + &mut world.tiles, + style, + view_region, + ); // self.update_metadata(state, tile_repository, queue); } } @@ -121,7 +130,7 @@ pub fn upload_system( } }*/ -fn upload_tesselated_layer( +fn upload_tessellated_layer( buffer_pool: &mut VectorBufferPool, _device: &wgpu::Device, queue: &wgpu::Queue, @@ -143,8 +152,9 @@ fn upload_tesselated_layer( .layers .iter() .flat_map(|data| match data { - VectorLayerData::Available(data) => Some(data), + VectorLayerData::AvailableLayer(data) => Some(data), VectorLayerData::Missing(_) => None, + VectorLayerData::AvailableSymbolLayer(_) => None // TODO when to upload symbol layers? }) .filter(|data| !loaded_layers.contains(data.source_layer.as_str())) .collect::>(); @@ -170,6 +180,7 @@ fn upload_tesselated_layer( .and_then(|paint| paint.get_color()) .map(|color| color.into()); + // Assign every feature in the layer the color from the style let feature_metadata = (0..feature_indices.len()) // FIXME: Iterate over actual features .enumerate() .flat_map(|(i, _feature)| { @@ -192,3 +203,81 @@ fn upload_tesselated_layer( } } } + + +// TODO cleanup +fn upload_symbol_layer( + buffer_pool: &mut SymbolBufferPool, + _device: &wgpu::Device, + queue: &wgpu::Queue, + tiles: &mut Tiles, + style: &Style, + view_region: &ViewRegion, +) { + // Upload all tessellated layers which are in view + for coords in view_region.iter() { + let Some(vector_layers) = tiles.query_mut::<&VectorLayersDataComponent>(coords) else { + continue; + }; + + let loaded_layers = buffer_pool + .get_loaded_source_layers_at(coords) + .unwrap_or_default(); + + let available_layers = vector_layers + .layers + .iter() + .flat_map(|data| match data { + VectorLayerData::AvailableSymbolLayer(data) => Some(data), + VectorLayerData::Missing(_) => None, + VectorLayerData::AvailableLayer(_) => None // TODO when to upload symbol layers? + }) + .filter(|data| !loaded_layers.contains(data.source_layer.as_str())) + .collect::>(); + + for style_layer in &style.layers { + let source_layer = style_layer.source_layer.as_ref().unwrap(); // TODO: Unwrap + + let Some(AvailableSymbolVectorLayerData { + coords, + feature_indices, + buffer, + .. + }) = available_layers + .iter() + .find(|layer| source_layer.as_str() == layer.source_layer) + else { + continue; + }; + + + // Assign every feature in the layer the color from the style + let feature_metadata = (0..feature_indices.len()) // FIXME: Iterate over actual features + .enumerate() + .flat_map(|(i, _feature)| { + iter::repeat(crate::render::shaders::ShaderFeatureStyle { + color: Vec4f32::default(), + }) + .take(feature_indices[i] as usize) + }) + .collect::>(); + + // FIXME + if buffer.buffer.indices.is_empty() { + log::error!("empty indices"); + log::error!("empty indices"); + continue + } + + log::debug!("Allocating geometry at {coords}"); + buffer_pool.allocate_layer_geometry( + queue, + *coords, + style_layer.clone(), + buffer, + ShaderLayerMetadata::new(style_layer.index as f32), + &feature_metadata, + ); + } + } +} From 7c720706affd00c824072db42d4bc6fb6a33778c Mon Sep 17 00:00:00 2001 From: Max Ammann Date: Mon, 5 Aug 2024 11:44:23 +0100 Subject: [PATCH 06/78] Extract SDF rendering into a plugin --- maplibre-winit/src/noweb.rs | 3 + maplibre/src/lib.rs | 2 + maplibre/src/sdf/mod.rs | 107 ++++++++ maplibre/src/sdf/populate_world_system.rs | 54 ++++ maplibre/src/sdf/queue_system.rs | 61 +++++ maplibre/src/sdf/render_commands.rs | 114 +++++++++ .../{vector => sdf}/resource/glyph_texture.rs | 0 maplibre/src/sdf/resource/mod.rs | 2 + maplibre/src/sdf/resource_system.rs | 115 +++++++++ maplibre/src/sdf/tessellation.rs | 241 ++++++++++++++++++ maplibre/src/{vector => sdf}/text.rs | 0 maplibre/src/sdf/upload_system.rs | 125 +++++++++ maplibre/src/vector/mod.rs | 63 +---- maplibre/src/vector/populate_world_system.rs | 14 - maplibre/src/vector/process_vector.rs | 2 +- maplibre/src/vector/queue_system.rs | 18 -- maplibre/src/vector/render_commands.rs | 104 -------- maplibre/src/vector/request_system.rs | 4 +- maplibre/src/vector/resource/mod.rs | 3 +- maplibre/src/vector/resource_system.rs | 94 +------ maplibre/src/vector/tessellation.rs | 232 +---------------- maplibre/src/vector/transferables.rs | 2 +- maplibre/src/vector/upload_system.rs | 92 +------ 23 files changed, 849 insertions(+), 603 deletions(-) create mode 100644 maplibre/src/sdf/mod.rs create mode 100644 maplibre/src/sdf/populate_world_system.rs create mode 100644 maplibre/src/sdf/queue_system.rs create mode 100644 maplibre/src/sdf/render_commands.rs rename maplibre/src/{vector => sdf}/resource/glyph_texture.rs (100%) create mode 100644 maplibre/src/sdf/resource/mod.rs create mode 100644 maplibre/src/sdf/resource_system.rs create mode 100644 maplibre/src/sdf/tessellation.rs rename maplibre/src/{vector => sdf}/text.rs (100%) create mode 100644 maplibre/src/sdf/upload_system.rs diff --git a/maplibre-winit/src/noweb.rs b/maplibre-winit/src/noweb.rs index 339b593c9..627fc9bf1 100644 --- a/maplibre-winit/src/noweb.rs +++ b/maplibre-winit/src/noweb.rs @@ -136,6 +136,9 @@ pub fn run_headed_map

( Box::new(maplibre::vector::VectorPlugin::< maplibre::vector::DefaultVectorTransferables, >::default()), + Box::new(maplibre::sdf::SdfPlugin::< + maplibre::vector::DefaultVectorTransferables, + >::default()), // Box::new(maplibre::raster::RasterPlugin::< // maplibre::raster::DefaultRasterTransferables, // >::default()), diff --git a/maplibre/src/lib.rs b/maplibre/src/lib.rs index a57e465ea..6ecb8ad7b 100644 --- a/maplibre/src/lib.rs +++ b/maplibre/src/lib.rs @@ -53,3 +53,5 @@ pub mod tcs; pub mod debug; pub mod raster; pub mod vector; + +pub mod sdf; diff --git a/maplibre/src/sdf/mod.rs b/maplibre/src/sdf/mod.rs new file mode 100644 index 000000000..605fc76a1 --- /dev/null +++ b/maplibre/src/sdf/mod.rs @@ -0,0 +1,107 @@ +use std::{marker::PhantomData, ops::Deref, rc::Rc}; + +use crate::{ + coords::WorldTileCoords, + environment::Environment, + kernel::Kernel, + plugin::Plugin, + render::{ + eventually::Eventually, + shaders::{ShaderFeatureStyle, ShaderLayerMetadata} + }, + schedule::Schedule, + tcs::{tiles::TileComponent, world::World}, + vector::resource::BufferPool, + vector::tessellation::{IndexDataType, OverAlignedVertexBuffer}, +}; +use crate::render::graph::RenderGraph; +use crate::render::RenderStageLabel; +use crate::render::shaders::SymbolVertex; +use crate::sdf::resource::GlyphTexture; +use crate::tcs::system::SystemContainer; +use crate::vector::VectorTransferables; + +mod populate_world_system; +mod queue_system; +mod render_commands; +mod resource; +mod resource_system; +mod upload_system; + +// Public due to bechmarks +pub mod tessellation; +mod text; + +struct SymbolPipeline(wgpu::RenderPipeline); + +impl Deref for SymbolPipeline { + type Target = wgpu::RenderPipeline; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub type SymbolBufferPool = BufferPool< + wgpu::Queue, + wgpu::Buffer, + SymbolVertex, + IndexDataType, + ShaderLayerMetadata, + ShaderFeatureStyle, +>; + +pub struct SdfPlugin(PhantomData); + +impl Default for SdfPlugin { + fn default() -> Self { + Self(Default::default()) + } +} + +impl Plugin for SdfPlugin { + fn build( + &self, + schedule: &mut Schedule, + kernel: Rc>, + world: &mut World, + _graph: &mut RenderGraph, + ) { + let resources = &mut world.resources; + + resources.insert(Eventually::::Uninitialized); + resources.insert(Eventually::::Uninitialized); + resources.insert(Eventually::::Uninitialized); + resources.insert(Eventually::<(wgpu::Texture, wgpu::Sampler)>::Uninitialized); + + schedule.add_system_to_stage( + RenderStageLabel::Extract, + SystemContainer::new(crate::sdf::populate_world_system::PopulateWorldSystem::::new(&kernel)), + ); + + schedule.add_system_to_stage(RenderStageLabel::Prepare, crate::sdf::resource_system::resource_system); + schedule.add_system_to_stage(RenderStageLabel::Queue, crate::sdf::upload_system::upload_system); // FIXME tcs: Upload updates the TileView in tileviewpattern -> upload most run before prepare + schedule.add_system_to_stage(RenderStageLabel::Queue, crate::sdf::queue_system::queue_system); + } +} + + +pub struct AvailableSymbolVectorLayerData { + pub coords: WorldTileCoords, + pub source_layer: String, + pub buffer: OverAlignedVertexBuffer, + /// Holds for each feature the count of indices. + pub feature_indices: Vec, +} + +pub enum SymbolLayerData { + AvailableSymbolLayer(AvailableSymbolVectorLayerData), +} + +#[derive(Default)] +pub struct SymbolLayersDataComponent { + pub done: bool, + pub layers: Vec, +} + +impl TileComponent for SymbolLayersDataComponent {} diff --git a/maplibre/src/sdf/populate_world_system.rs b/maplibre/src/sdf/populate_world_system.rs new file mode 100644 index 000000000..7293baec1 --- /dev/null +++ b/maplibre/src/sdf/populate_world_system.rs @@ -0,0 +1,54 @@ +use std::{borrow::Cow, marker::PhantomData, rc::Rc}; + +use crate::{ + context::MapContext, + environment::Environment, + io::apc::{AsyncProcedureCall, Message}, + kernel::Kernel, + tcs::system::System, + vector::transferables::*, +}; +use crate::sdf::{SymbolLayerData, SymbolLayersDataComponent}; + +pub struct PopulateWorldSystem { + kernel: Rc>, + phantom_t: PhantomData, +} + +impl PopulateWorldSystem { + pub fn new(kernel: &Rc>) -> Self { + Self { + kernel: kernel.clone(), + phantom_t: Default::default(), + } + } +} + +impl System for PopulateWorldSystem { + fn name(&self) -> Cow<'static, str> { + "sdf_populate_world_system".into() + } + + fn run(&mut self, MapContext { world, .. }: &mut MapContext) { + for message in self.kernel.apc().receive(|message| { + message.has_tag(T::SymbolLayerTessellated::message_tag()) + || message.has_tag(T::LayerIndexed::message_tag()) + }) { + let message: Message = message; + if message.has_tag(T::SymbolLayerTessellated::message_tag()) { + let message = message.into_transferable::(); + + let Some(component) = world + .tiles + .query_mut::<&mut SymbolLayersDataComponent>(message.coords()) + else { + continue; + }; + + component + .layers + .push(SymbolLayerData::AvailableSymbolLayer(message.to_layer())); + } + } + } +} diff --git a/maplibre/src/sdf/queue_system.rs b/maplibre/src/sdf/queue_system.rs new file mode 100644 index 000000000..80f9cb646 --- /dev/null +++ b/maplibre/src/sdf/queue_system.rs @@ -0,0 +1,61 @@ +//! Queues [PhaseItems](crate::render::render_phase::PhaseItem) for rendering. +use crate::{ + context::MapContext, + render::{ + eventually::{Eventually, Eventually::Initialized}, + render_commands::DrawMasks, + render_phase::{DrawState, LayerItem, RenderPhase, TileMaskItem}, + tile_view_pattern::WgpuTileViewPattern, + }, + tcs::tiles::Tile, +}; +use crate::sdf::render_commands::DrawSymbols; +use crate::sdf::SymbolBufferPool; + + +pub fn queue_system(MapContext { world, .. }: &mut MapContext) { + let Some(( + Initialized(tile_view_pattern), + mask_phase, + layer_item_phase, + Initialized(symbol_buffer_pool) + )) = world.resources.query_mut::<( + &mut Eventually, + &mut RenderPhase, + &mut RenderPhase, + &mut Eventually, + )>() + else { + return; + }; + + + for view_tile in tile_view_pattern.iter() { + let coords = &view_tile.coords(); + tracing::trace!("Drawing tile at {coords}"); + + // draw tile normal or the source e.g. parent or children + view_tile.render(|source_shape| { + // Draw masks for all source_shapes + mask_phase.add(TileMaskItem { + draw_function: Box::new(DrawState::::new()), + source_shape: source_shape.clone(), + }); + + if let Some(layer_entries) = symbol_buffer_pool.index().get_layers(source_shape.coords()) { + for layer_entry in layer_entries { + // Draw tile + layer_item_phase.add(LayerItem { + draw_function: Box::new(DrawState::::new()), + index: layer_entry.style_layer.index, + style_layer: layer_entry.style_layer.id.clone(), + tile: Tile { + coords: layer_entry.coords, + }, + source_shape: source_shape.clone(), + }); + } + }; + }); + } +} diff --git a/maplibre/src/sdf/render_commands.rs b/maplibre/src/sdf/render_commands.rs new file mode 100644 index 000000000..c0c288a51 --- /dev/null +++ b/maplibre/src/sdf/render_commands.rs @@ -0,0 +1,114 @@ + +use crate::{ + render::{ + eventually::{Eventually, Eventually::Initialized}, + render_phase::{LayerItem, PhaseItem, RenderCommand, RenderCommandResult}, + resource::TrackedRenderPass, + tile_view_pattern::WgpuTileViewPattern, + INDEX_FORMAT, + }, + tcs::world::World, +}; +use crate::sdf::resource::GlyphTexture; +use crate::sdf::{SymbolBufferPool, SymbolPipeline}; + +pub struct SetSymbolPipeline; +impl RenderCommand

for SetSymbolPipeline { + fn render<'w>( + world: &'w World, + _item: &P, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let Some((Initialized(GlyphTexture { ref bind_group, .. }), Initialized(symbol_pipeline))) = + world.resources.query::<( + &Eventually, + &Eventually, + )>() + else { + return RenderCommandResult::Failure; + }; + + pass.set_bind_group(0, bind_group, &[]); + pass.set_render_pipeline(symbol_pipeline); + RenderCommandResult::Success + } +} + +pub struct DrawSymbol; +impl RenderCommand for DrawSymbol { + fn render<'w>( + world: &'w World, + item: &LayerItem, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let Some((Initialized(symbol_buffer_pool), Initialized(tile_view_pattern))) = + world.resources.query::<( + &Eventually, + &Eventually, + )>() + else { + return RenderCommandResult::Failure; + }; + + let Some(vector_layers) = symbol_buffer_pool.index().get_layers(item.tile.coords) else { + return RenderCommandResult::Failure; + }; + + let Some(entry) = vector_layers + .iter() + .find(|entry| entry.style_layer.id == item.style_layer) + else { + return RenderCommandResult::Failure; + }; + + let source_shape = &item.source_shape; + + let tile_view_pattern_buffer = source_shape + .buffer_range() + .expect("tile_view_pattern needs to be uploaded first"); // FIXME tcs + + // Uses stencil value of requested tile and the shape of the requested tile + let reference = source_shape.coords().stencil_reference_value_3d() as u32; + + tracing::trace!( + "Drawing layer {:?} at {}", + entry.style_layer.source_layer, + entry.coords + ); + + let index_range = entry.indices_buffer_range(); + + if index_range.is_empty() { + tracing::error!("Tried to draw a vector tile without any vertices"); + return RenderCommandResult::Failure; + } + + pass.set_stencil_reference(reference); + + pass.set_index_buffer( + symbol_buffer_pool + .indices() + .slice(index_range), + INDEX_FORMAT, + ); + pass.set_vertex_buffer( + 0, + symbol_buffer_pool + .vertices() + .slice(entry.vertices_buffer_range()), + ); + pass.set_vertex_buffer(1, tile_view_pattern.buffer().slice(tile_view_pattern_buffer)); + pass.set_vertex_buffer( + 2, + symbol_buffer_pool + .metadata() + .slice(entry.layer_metadata_buffer_range()), + ); + + pass.draw_indexed(entry.indices_range(), 0, 0..1); + RenderCommandResult::Success + } +} + + +pub type DrawSymbols = (SetSymbolPipeline, DrawSymbol); \ No newline at end of file diff --git a/maplibre/src/vector/resource/glyph_texture.rs b/maplibre/src/sdf/resource/glyph_texture.rs similarity index 100% rename from maplibre/src/vector/resource/glyph_texture.rs rename to maplibre/src/sdf/resource/glyph_texture.rs diff --git a/maplibre/src/sdf/resource/mod.rs b/maplibre/src/sdf/resource/mod.rs new file mode 100644 index 000000000..9aee747dd --- /dev/null +++ b/maplibre/src/sdf/resource/mod.rs @@ -0,0 +1,2 @@ +pub use glyph_texture::*; +mod glyph_texture; diff --git a/maplibre/src/sdf/resource_system.rs b/maplibre/src/sdf/resource_system.rs new file mode 100644 index 000000000..0d021c3c7 --- /dev/null +++ b/maplibre/src/sdf/resource_system.rs @@ -0,0 +1,115 @@ +//! Prepares GPU-owned resources by initializing them if they are uninitialized or out-of-date. +use wgpu::util::{DeviceExt, TextureDataOrder}; +use crate::{ + context::MapContext, + render::{ + eventually::Eventually, + resource::{RenderPipeline, TilePipeline}, + shaders, + shaders::Shader, + RenderResources, Renderer, + }, +}; +use crate::sdf::{SymbolBufferPool, SymbolPipeline}; +use crate::sdf::resource::GlyphTexture; +use crate::sdf::text::GlyphSet; +use crate::vector::resource::BufferPool; + + +pub fn resource_system( + MapContext { + world, + renderer: + Renderer { + device, + queue, + resources: RenderResources { surface, .. }, + settings, + .. + }, + .. + }: &mut MapContext, +) { + let Some((symbol_buffer_pool, symbol_pipeline, glyph_texture_sampler, glyph_texture_bind_group)) = world.resources.query_mut::<( + &mut Eventually, + &mut Eventually, + &mut Eventually<(wgpu::Texture, wgpu::Sampler)>, + &mut Eventually, + )>() else { + return; + }; + + symbol_buffer_pool.initialize(|| BufferPool::from_device(device)); + + symbol_pipeline.initialize(|| { + let tile_shader = shaders::SymbolTileShader { + format: surface.surface_format(), + }; + + let pipeline = TilePipeline::new( + "symbol_pipeline".into(), + *settings, + tile_shader.describe_vertex(), + tile_shader.describe_fragment(), + true, + false, + true, // TODO ignore tile mask + false, + surface.is_multisampling_supported(settings.msaa), + false, + true + ) + .describe_render_pipeline() + .initialize(device); + + + let (texture, sampler) = glyph_texture_sampler.initialize(|| { + let data = std::fs::read("./data/0-255.pbf").unwrap(); + let glyphs = GlyphSet::try_from( + data.as_slice(), + ).unwrap(); + + let (width, height) = glyphs.get_texture_dimensions(); + + let texture = device.create_texture_with_data( + &queue, + &wgpu::TextureDescriptor { + label: Some("Glyph Texture"), + size: wgpu::Extent3d { + width: width as _, + height: height as _, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::R8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[wgpu::TextureFormat::R8Unorm], // TODO + }, + TextureDataOrder::LayerMajor, // TODO + glyphs.get_texture_bytes(), + ); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + // SDF rendering requires linear interpolation + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + + (texture, sampler) + }); + + glyph_texture_bind_group.initialize(|| { + GlyphTexture::from_device( + device, + texture, + sampler, + &pipeline.get_bind_group_layout(0), + ) + }); + + SymbolPipeline(pipeline) + }); +} diff --git a/maplibre/src/sdf/tessellation.rs b/maplibre/src/sdf/tessellation.rs new file mode 100644 index 000000000..c2779eb23 --- /dev/null +++ b/maplibre/src/sdf/tessellation.rs @@ -0,0 +1,241 @@ +//! Tessellation for lines and polygons is implemented here. + +use std::fs; + +use csscolorparser::Color; +use geozero::{FeatureProcessor, GeomProcessor, PropertyProcessor}; +use geozero::ColumnValue; +use lyon::geom::{Box2D, euclid::Point2D}; +use lyon::tessellation::{ + BuffersBuilder, FillOptions, FillTessellator, geometry_builder::MaxIndex + , +}; +use lyon::tessellation::VertexBuffers; + +use crate::render::shaders::SymbolVertex; +use crate::sdf::text::{Anchor, GlyphSet, SymbolVertexBuilder}; + +const DEFAULT_TOLERANCE: f32 = 0.02; + +/// Vertex buffers index data type. +pub type IndexDataType = u32; // Must match INDEX_FORMAT + +type GeoResult = geozero::error::Result; + +/// Build tessellations with vectors. +pub struct TextTessellator + MaxIndex> { + glyphs: GlyphSet, + + // output + pub quad_buffer: VertexBuffers, + pub feature_indices: Vec, + + // iteration variables + current_index: usize, + current_text: Option, + current_bbox: Option>, +} + +impl + MaxIndex> Default +for TextTessellator +{ + fn default() -> Self { + let data = fs::read("./data/0-255.pbf").unwrap(); + let glyphs = GlyphSet::try_from(data.as_slice()).unwrap(); + Self { + glyphs, + quad_buffer: VertexBuffers::new(), + feature_indices: Vec::new(), + current_index: 0, + current_text: None, + current_bbox: None, + } + } +} + +impl + MaxIndex> TextTessellator { + pub fn tessellate_glyph_quads( + &mut self, + origin: [f32; 2], + label_text: &str, + color: Color, + ) -> Option> { + let mut tessellator = FillTessellator::new(); + + let mut next_origin = origin; + + let texture_dimensions = self.glyphs.get_texture_dimensions(); + let texture_dimensions = (texture_dimensions.0 as f32, texture_dimensions.1 as f32); + + // TODO: silently drops unknown characters + // TODO: handle line wrapping / line height + let mut bbox = None; + for glyph in label_text + .chars() + .filter_map(|c| self.glyphs.glyphs.get(&c)) + .collect::>() + { + let glyph_dims = glyph.buffered_dimensions(); + let width = glyph_dims.0 as f32; + let height = glyph_dims.1 as f32; + + let glyph_anchor = [ + next_origin[0] + glyph.left_bearing as f32, + next_origin[1] - glyph.top_bearing as f32, + 0., + ]; + + let glyph_rect = Box2D::new( + (glyph_anchor[0], glyph_anchor[1]).into(), + (glyph_anchor[0] + width, glyph_anchor[1] + height).into(), + ); + + bbox = bbox.map_or_else( + || Some(glyph_rect), + |bbox: Box2D<_>| Some(bbox.union(&glyph_rect)), + ); + + tessellator + .tessellate_rectangle( + &glyph_rect, + &FillOptions::default(), + &mut BuffersBuilder::new( + &mut self.quad_buffer, + SymbolVertexBuilder { + glyph_anchor, + text_anchor: [origin[0], origin[1], 0.0], + texture_dimensions, + sprite_dimensions: (width, height), + sprite_offset: ( + glyph.origin_offset().0 as f32, + glyph.origin_offset().1 as f32, + ), + color: color.to_rgba8(), // TODO: is this conversion oke? + glyph: true, // Set here to true to use SDF rendering + }, + ), + ) + .ok()?; + + next_origin[0] += glyph.advance() as f32; + } + + bbox + } +} + +impl + MaxIndex> GeomProcessor +for TextTessellator +{ + fn xy(&mut self, x: f64, y: f64, _idx: usize) -> GeoResult<()> { + let new_box = Box2D::new( + Point2D::new(x as f32, y as f32), + Point2D::new(x as f32, y as f32), + ); + if let Some(bbox) = self.current_bbox { + self.current_bbox = Some(bbox.union(&new_box)) + } else { + self.current_bbox = Some(new_box) + } + Ok(()) + } + + fn point_begin(&mut self, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn point_end(&mut self, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn multipoint_begin(&mut self, _size: usize, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn multipoint_end(&mut self, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn linestring_begin(&mut self, _tagged: bool, _size: usize, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn linestring_end(&mut self, _tagged: bool, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn multilinestring_begin(&mut self, _size: usize, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn multilinestring_end(&mut self, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn polygon_begin(&mut self, _tagged: bool, _size: usize, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn polygon_end(&mut self, _tagged: bool, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn multipolygon_begin(&mut self, _size: usize, _idx: usize) -> GeoResult<()> { + Ok(()) + } + + fn multipolygon_end(&mut self, _idx: usize) -> GeoResult<()> { + Ok(()) + } +} + +impl + MaxIndex> PropertyProcessor +for TextTessellator +{ + fn property( + &mut self, + _idx: usize, + name: &str, + value: &ColumnValue, + ) -> geozero::error::Result { + if name == "name" { // TODO: Support different tags + match value { + ColumnValue::String(str) => { + self.current_text = Some(str.to_string()); + } + _ => {} + } + } + Ok(true) + } +} + +impl + MaxIndex> FeatureProcessor +for TextTessellator +{ + fn feature_end(&mut self, _idx: u64) -> geozero::error::Result<()> { + if let (Some(bbox), Some(text)) = (&self.current_bbox, self.current_text.clone()) { + let anchor = Anchor::Center; + // TODO: add more anchor possibilities; only support center right now + // TODO: document how anchor and glyph metrics work together to establish a baseline + let origin = match anchor { + Anchor::Center => bbox.center().to_array(), + _ => unimplemented!("no support for this anchor"), + }; + self.tessellate_glyph_quads( + origin, + text.as_str(), + Color::from_linear_rgba(1.0, 0., 0., 1.), + ); + + let next_index = self.quad_buffer.indices.len(); + let indices = (next_index - self.current_index) as u32; + self.feature_indices.push(indices); + self.current_index = next_index; + } + + self.current_bbox = None; + self.current_text = None; + Ok(()) + } +} diff --git a/maplibre/src/vector/text.rs b/maplibre/src/sdf/text.rs similarity index 100% rename from maplibre/src/vector/text.rs rename to maplibre/src/sdf/text.rs diff --git a/maplibre/src/sdf/upload_system.rs b/maplibre/src/sdf/upload_system.rs new file mode 100644 index 000000000..b8a7bfd62 --- /dev/null +++ b/maplibre/src/sdf/upload_system.rs @@ -0,0 +1,125 @@ +//! Uploads data to the GPU which is needed for rendering. + +use std::iter; + +use crate::{ + context::MapContext, + coords::ViewRegion, + render::{ + eventually::{Eventually, Eventually::Initialized}, + shaders::{Vec4f32}, + tile_view_pattern::DEFAULT_TILE_SIZE, + Renderer, + }, + style::Style, + tcs::tiles::Tiles, +}; +use crate::render::shaders::ShaderLayerMetadata; +use crate::sdf::{AvailableSymbolVectorLayerData, SymbolBufferPool, SymbolLayerData, SymbolLayersDataComponent}; + +pub fn upload_system( + MapContext { + world, + style, + view_state, + renderer: Renderer { device, queue, .. }, + .. + }: &mut MapContext, +) { + let Some(Initialized(symbol_buffer_pool)) = world + .resources + .query_mut::<&mut Eventually>() + else { + return; + }; + + let view_region = + view_state.create_view_region(view_state.zoom().zoom_level(DEFAULT_TILE_SIZE)); + + if let Some(view_region) = &view_region { + upload_symbol_layer( + symbol_buffer_pool, + device, + queue, + &mut world.tiles, + style, + view_region, + ); + // self.update_metadata(state, tile_repository, queue); + } +} + +// TODO cleanup, duplicated +fn upload_symbol_layer( + buffer_pool: &mut SymbolBufferPool, + _device: &wgpu::Device, + queue: &wgpu::Queue, + tiles: &mut Tiles, + style: &Style, + view_region: &ViewRegion, +) { + // Upload all tessellated layers which are in view + for coords in view_region.iter() { + let Some(vector_layers) = tiles.query_mut::<&SymbolLayersDataComponent>(coords) else { + continue; + }; + + let loaded_layers = buffer_pool + .get_loaded_source_layers_at(coords) + .unwrap_or_default(); + + let available_layers = vector_layers + .layers + .iter() + .flat_map(|data| match data { + SymbolLayerData::AvailableSymbolLayer(data) => Some(data), + }) + .filter(|data| !loaded_layers.contains(data.source_layer.as_str())) + .collect::>(); + + for style_layer in &style.layers { + let source_layer = style_layer.source_layer.as_ref().unwrap(); // TODO: Unwrap + + let Some(AvailableSymbolVectorLayerData { + coords, + feature_indices, + buffer, + .. + }) = available_layers + .iter() + .find(|layer| source_layer.as_str() == layer.source_layer) + else { + continue; + }; + + + // Assign every feature in the layer the color from the style + let feature_metadata = (0..feature_indices.len()) // FIXME: Iterate over actual features + .enumerate() + .flat_map(|(i, _feature)| { + iter::repeat(crate::render::shaders::ShaderFeatureStyle { + color: Vec4f32::default(), + }) + .take(feature_indices[i] as usize) + }) + .collect::>(); + + // FIXME + if buffer.buffer.indices.is_empty() { + log::error!("empty indices"); + log::error!("empty indices"); + continue + } + + log::debug!("Allocating geometry at {coords}"); + buffer_pool.allocate_layer_geometry( + queue, + *coords, + style_layer.clone(), + buffer, + ShaderLayerMetadata::new(style_layer.index as f32), + &feature_metadata, + ); + } + } +} diff --git a/maplibre/src/vector/mod.rs b/maplibre/src/vector/mod.rs index 7be1ade8d..aea3f2fd8 100644 --- a/maplibre/src/vector/mod.rs +++ b/maplibre/src/vector/mod.rs @@ -1,5 +1,11 @@ use std::{marker::PhantomData, ops::Deref, rc::Rc}; +pub use process_vector::*; +pub use transferables::{ + DefaultVectorTransferables, LayerIndexed, LayerMissing, LayerTessellated, TileTessellated, + VectorTransferables, +}; + use crate::{ coords::WorldTileCoords, environment::Environment, @@ -7,43 +13,33 @@ use crate::{ plugin::Plugin, render::{ eventually::Eventually, + RenderStageLabel, shaders::{ShaderFeatureStyle, ShaderLayerMetadata}, - tile_view_pattern::{HasTile, ViewTileSources}, - RenderStageLabel, ShaderVertex, + ShaderVertex, tile_view_pattern::{HasTile, ViewTileSources}, }, schedule::Schedule, tcs::{system::SystemContainer, tiles::TileComponent, world::World}, - vector::tessellation::{IndexDataType, OverAlignedVertexBuffer}, vector::{ populate_world_system::PopulateWorldSystem, queue_system::queue_system, request_system::RequestSystem, resource::BufferPool, resource_system::resource_system, upload_system::upload_system, }, + vector::tessellation::{IndexDataType, OverAlignedVertexBuffer}, }; +use crate::render::graph::RenderGraph; mod populate_world_system; mod process_vector; mod queue_system; mod render_commands; mod request_system; -mod resource; +pub(crate) mod resource; mod resource_system; -mod transferables; +pub(crate) mod transferables; mod upload_system; // Public due to bechmarks pub mod tessellation; -mod text; - -pub use process_vector::*; -pub use transferables::{ - DefaultVectorTransferables, LayerIndexed, LayerMissing, LayerTessellated, TileTessellated, - VectorTransferables, -}; - -use crate::render::graph::RenderGraph; -use crate::render::shaders::SymbolVertex; -use crate::vector::resource::GlyphTexture; struct VectorPipeline(wgpu::RenderPipeline); impl Deref for VectorPipeline { @@ -63,25 +59,6 @@ pub type VectorBufferPool = BufferPool< ShaderFeatureStyle, >; -struct SymbolPipeline(wgpu::RenderPipeline); - -impl Deref for SymbolPipeline { - type Target = wgpu::RenderPipeline; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -pub type SymbolBufferPool = BufferPool< - wgpu::Queue, - wgpu::Buffer, - SymbolVertex, - IndexDataType, - ShaderLayerMetadata, - ShaderFeatureStyle, ->; - pub struct VectorPlugin(PhantomData); impl Default for VectorPlugin { @@ -118,12 +95,6 @@ impl Plugin for VectorPlugin { resources.insert(Eventually::::Uninitialized); resources.insert(Eventually::::Uninitialized); - - resources.insert(Eventually::::Uninitialized); - resources.insert(Eventually::::Uninitialized); - resources.insert(Eventually::::Uninitialized); - resources.insert(Eventually::<(wgpu::Texture, wgpu::Sampler)>::Uninitialized); - resources .get_or_init_mut::() .add_resource_query::<&Eventually>() @@ -152,15 +123,6 @@ pub struct AvailableVectorLayerData { pub feature_indices: Vec, } -pub struct AvailableSymbolVectorLayerData { - pub coords: WorldTileCoords, - pub source_layer: String, - pub buffer: OverAlignedVertexBuffer, - /// Holds for each feature the count of indices. - pub feature_indices: Vec, -} - - pub struct MissingVectorLayerData { pub coords: WorldTileCoords, pub source_layer: String, @@ -168,7 +130,6 @@ pub struct MissingVectorLayerData { pub enum VectorLayerData { AvailableLayer(AvailableVectorLayerData), - AvailableSymbolLayer(AvailableSymbolVectorLayerData), Missing(MissingVectorLayerData), } diff --git a/maplibre/src/vector/populate_world_system.rs b/maplibre/src/vector/populate_world_system.rs index 26e25d26f..40af8351d 100644 --- a/maplibre/src/vector/populate_world_system.rs +++ b/maplibre/src/vector/populate_world_system.rs @@ -33,7 +33,6 @@ impl System for PopulateWorldSystem System for PopulateWorldSystem(); - - let Some(component) = world - .tiles - .query_mut::<&mut VectorLayersDataComponent>(message.coords()) - else { - continue; - }; - - component - .layers - .push(VectorLayerData::AvailableSymbolLayer(message.to_layer())); } else if message.has_tag(T::LayerIndexed::message_tag()) { let message = message.into_transferable::(); world diff --git a/maplibre/src/vector/process_vector.rs b/maplibre/src/vector/process_vector.rs index 0a4d3c240..ed5c0b2d3 100644 --- a/maplibre/src/vector/process_vector.rs +++ b/maplibre/src/vector/process_vector.rs @@ -19,8 +19,8 @@ use crate::{ }, }; use crate::render::shaders::SymbolVertex; +use crate::sdf::tessellation::TextTessellator; use crate::style::layer::{LayerPaint, StyleLayer}; -use crate::vector::tessellation::TextTessellator; use crate::vector::transferables::SymbolLayerTessellated; #[derive(Error, Debug)] diff --git a/maplibre/src/vector/queue_system.rs b/maplibre/src/vector/queue_system.rs index cc1cbc7d5..8140ef796 100644 --- a/maplibre/src/vector/queue_system.rs +++ b/maplibre/src/vector/queue_system.rs @@ -10,8 +10,6 @@ use crate::{ tcs::tiles::Tile, vector::{render_commands::DrawVectorTiles, VectorBufferPool}, }; -use crate::vector::render_commands::DrawSymbols; -use crate::vector::SymbolBufferPool; pub fn queue_system(MapContext { world, .. }: &mut MapContext) { let Some(( @@ -19,13 +17,11 @@ pub fn queue_system(MapContext { world, .. }: &mut MapContext) { Initialized(buffer_pool), mask_phase, layer_item_phase, - Initialized(symbol_buffer_pool) )) = world.resources.query_mut::<( &mut Eventually, &mut Eventually, &mut RenderPhase, &mut RenderPhase, - &mut Eventually, )>() else { return; @@ -45,20 +41,6 @@ pub fn queue_system(MapContext { world, .. }: &mut MapContext) { source_shape: source_shape.clone(), }); - if let Some(layer_entries) = symbol_buffer_pool.index().get_layers(source_shape.coords()) { - for layer_entry in layer_entries { - // Draw tile - layer_item_phase.add(LayerItem { - draw_function: Box::new(DrawState::::new()), - index: layer_entry.style_layer.index, - style_layer: layer_entry.style_layer.id.clone(), - tile: Tile { - coords: layer_entry.coords, - }, - source_shape: source_shape.clone(), - }); - } - }; if let Some(layer_entries) = buffer_pool_index.get_layers(source_shape.coords()) { for layer_entry in layer_entries { // continue; diff --git a/maplibre/src/vector/render_commands.rs b/maplibre/src/vector/render_commands.rs index 3fa36695f..9a6166727 100644 --- a/maplibre/src/vector/render_commands.rs +++ b/maplibre/src/vector/render_commands.rs @@ -11,8 +11,6 @@ use crate::{ tcs::world::World, vector::{VectorBufferPool, VectorPipeline}, }; -use crate::vector::resource::GlyphTexture; -use crate::vector::{SymbolBufferPool, SymbolPipeline}; pub struct SetVectorTilePipeline; impl RenderCommand

for SetVectorTilePipeline { @@ -109,105 +107,3 @@ impl RenderCommand for DrawVectorTile { } pub type DrawVectorTiles = (SetVectorTilePipeline, DrawVectorTile); - - -pub struct SetSymbolPipeline; -impl RenderCommand

for SetSymbolPipeline { - fn render<'w>( - world: &'w World, - _item: &P, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - let Some((Initialized(GlyphTexture { ref bind_group, .. }), Initialized(symbol_pipeline))) = - world.resources.query::<( - &Eventually, - &Eventually, - )>() - else { - return RenderCommandResult::Failure; - }; - - pass.set_bind_group(0, bind_group, &[]); - pass.set_render_pipeline(symbol_pipeline); - RenderCommandResult::Success - } -} - -pub struct DrawSymbol; -impl RenderCommand for DrawSymbol { - fn render<'w>( - world: &'w World, - item: &LayerItem, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - let Some((Initialized(symbol_buffer_pool), Initialized(tile_view_pattern))) = - world.resources.query::<( - &Eventually, - &Eventually, - )>() - else { - return RenderCommandResult::Failure; - }; - - let Some(vector_layers) = symbol_buffer_pool.index().get_layers(item.tile.coords) else { - return RenderCommandResult::Failure; - }; - - let Some(entry) = vector_layers - .iter() - .find(|entry| entry.style_layer.id == item.style_layer) - else { - return RenderCommandResult::Failure; - }; - - let source_shape = &item.source_shape; - - let tile_view_pattern_buffer = source_shape - .buffer_range() - .expect("tile_view_pattern needs to be uploaded first"); // FIXME tcs - - // Uses stencil value of requested tile and the shape of the requested tile - let reference = source_shape.coords().stencil_reference_value_3d() as u32; - - tracing::trace!( - "Drawing layer {:?} at {}", - entry.style_layer.source_layer, - entry.coords - ); - - let index_range = entry.indices_buffer_range(); - - if index_range.is_empty() { - tracing::error!("Tried to draw a vector tile without any vertices"); - return RenderCommandResult::Failure; - } - - pass.set_stencil_reference(reference); - - pass.set_index_buffer( - symbol_buffer_pool - .indices() - .slice(index_range), - INDEX_FORMAT, - ); - pass.set_vertex_buffer( - 0, - symbol_buffer_pool - .vertices() - .slice(entry.vertices_buffer_range()), - ); - pass.set_vertex_buffer(1, tile_view_pattern.buffer().slice(tile_view_pattern_buffer)); - pass.set_vertex_buffer( - 2, - symbol_buffer_pool - .metadata() - .slice(entry.layer_metadata_buffer_range()), - ); - - pass.draw_indexed(entry.indices_range(), 0, 0..1); - RenderCommandResult::Success - } -} - - -pub type DrawSymbols = (SetSymbolPipeline, DrawSymbol); \ No newline at end of file diff --git a/maplibre/src/vector/request_system.rs b/maplibre/src/vector/request_system.rs index 24214244c..df41ff276 100644 --- a/maplibre/src/vector/request_system.rs +++ b/maplibre/src/vector/request_system.rs @@ -18,6 +18,7 @@ use crate::{ VectorLayersDataComponent, }, }; +use crate::sdf::SymbolLayersDataComponent; use crate::style::layer::StyleLayer; pub struct RequestSystem { @@ -74,7 +75,8 @@ impl System for RequestSystem { .tiles .spawn_mut(coords) .unwrap() - .insert(VectorLayersDataComponent::default()); + .insert(VectorLayersDataComponent::default()) + .insert(SymbolLayersDataComponent::default()); tracing::event!(tracing::Level::ERROR, %coords, "tile request started: {coords}"); log::info!("tile request started: {coords}"); diff --git a/maplibre/src/vector/resource/mod.rs b/maplibre/src/vector/resource/mod.rs index 207585d12..99242c462 100644 --- a/maplibre/src/vector/resource/mod.rs +++ b/maplibre/src/vector/resource/mod.rs @@ -1,5 +1,4 @@ pub use buffer_pool::*; -pub use glyph_texture::*; mod buffer_pool; -mod glyph_texture; + diff --git a/maplibre/src/vector/resource_system.rs b/maplibre/src/vector/resource_system.rs index ffd91b046..c71680614 100644 --- a/maplibre/src/vector/resource_system.rs +++ b/maplibre/src/vector/resource_system.rs @@ -1,19 +1,15 @@ //! Prepares GPU-owned resources by initializing them if they are uninitialized or out-of-date. -use wgpu::util::{DeviceExt, TextureDataOrder}; use crate::{ context::MapContext, render::{ eventually::Eventually, + Renderer, + RenderResources, resource::{RenderPipeline, TilePipeline}, - shaders, - shaders::Shader, - RenderResources, Renderer, + shaders, shaders::Shader, }, vector::{resource::BufferPool, VectorBufferPool, VectorPipeline}, }; -use crate::vector::{SymbolBufferPool, SymbolPipeline}; -use crate::vector::resource::GlyphTexture; -use crate::vector::text::GlyphSet; pub fn resource_system( MapContext { @@ -61,88 +57,4 @@ pub fn resource_system( VectorPipeline(pipeline) }); - - - let Some((symbol_buffer_pool, symbol_pipeline, glyph_texture_sampler, glyph_texture_bind_group)) = world.resources.query_mut::<( - &mut Eventually, - &mut Eventually, - &mut Eventually<(wgpu::Texture, wgpu::Sampler)>, - &mut Eventually, - )>() else { - return; - }; - - symbol_buffer_pool.initialize(|| BufferPool::from_device(device)); - - symbol_pipeline.initialize(|| { - let tile_shader = shaders::SymbolTileShader { - format: surface.surface_format(), - }; - - let pipeline = TilePipeline::new( - "symbol_pipeline".into(), - *settings, - tile_shader.describe_vertex(), - tile_shader.describe_fragment(), - true, - false, - true, // TODO ignore tile mask - false, - surface.is_multisampling_supported(settings.msaa), - false, - true - ) - .describe_render_pipeline() - .initialize(device); - - - let (texture, sampler) = glyph_texture_sampler.initialize(|| { - let data = std::fs::read("./data/0-255.pbf").unwrap(); - let glyphs = GlyphSet::try_from( - data.as_slice(), - ).unwrap(); - - let (width, height) = glyphs.get_texture_dimensions(); - - let texture = device.create_texture_with_data( - &queue, - &wgpu::TextureDescriptor { - label: Some("Glyph Texture"), - size: wgpu::Extent3d { - width: width as _, - height: height as _, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::R8Unorm, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - view_formats: &[wgpu::TextureFormat::R8Unorm], // TODO - }, - TextureDataOrder::LayerMajor, // TODO - glyphs.get_texture_bytes(), - ); - - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - // SDF rendering requires linear interpolation - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - ..Default::default() - }); - - (texture, sampler) - }); - - glyph_texture_bind_group.initialize(|| { - GlyphTexture::from_device( - device, - texture, - sampler, - &pipeline.get_bind_group_layout(0), - ) - }); - - SymbolPipeline(pipeline) - }); } diff --git a/maplibre/src/vector/tessellation.rs b/maplibre/src/vector/tessellation.rs index 08fb5ef6e..8621d7bae 100644 --- a/maplibre/src/vector/tessellation.rs +++ b/maplibre/src/vector/tessellation.rs @@ -11,14 +11,6 @@ use lyon::{ StrokeOptions, StrokeTessellator, }, }; -use std::fs; - -use csscolorparser::Color; -use geozero::{ColumnValue}; -use lyon::{ - geom::{euclid::Point2D, Box2D}, - -}; use crate::{ render::ShaderVertex, @@ -28,14 +20,14 @@ use bytemuck::Pod; use lyon::tessellation::{ FillVertex, FillVertexConstructor, StrokeVertex, StrokeVertexConstructor, VertexBuffers, }; -use crate::render::shaders::SymbolVertex; -use crate::vector::text::{Anchor, GlyphSet, SymbolVertexBuilder}; const DEFAULT_TOLERANCE: f32 = 0.02; /// Vertex buffers index data type. pub type IndexDataType = u32; // Must match INDEX_FORMAT +type GeoResult = geozero::error::Result; + /// Constructor for Fill and Stroke vertices. pub struct VertexConstructor {} @@ -133,9 +125,6 @@ impl Align for VertexBuffers { } } - -type GeoResult = geozero::error::Result; - /// Build tessellations with vectors. pub struct ZeroTessellator + MaxIndex> { path_builder: RefCell, @@ -316,220 +305,3 @@ for ZeroTessellator } } -/// Build tessellations with vectors. -pub struct TextTessellator + MaxIndex> { - glyphs: GlyphSet, - - // output - pub quad_buffer: VertexBuffers, - pub feature_indices: Vec, - - // iteration variables - current_index: usize, - current_text: Option, - current_bbox: Option>, -} - -impl + MaxIndex> Default -for TextTessellator -{ - fn default() -> Self { - let data = fs::read("./data/0-255.pbf").unwrap(); - let glyphs = GlyphSet::try_from(data.as_slice()).unwrap(); - Self { - glyphs, - quad_buffer: VertexBuffers::new(), - feature_indices: Vec::new(), - current_index: 0, - current_text: None, - current_bbox: None, - } - } -} - -impl + MaxIndex> TextTessellator { - pub fn tessellate_glyph_quads( - &mut self, - origin: [f32; 2], - label_text: &str, - color: Color, - ) -> Option> { - let mut tessellator = FillTessellator::new(); - - let mut next_origin = origin; - - let texture_dimensions = self.glyphs.get_texture_dimensions(); - let texture_dimensions = (texture_dimensions.0 as f32, texture_dimensions.1 as f32); - - // TODO: silently drops unknown characters - // TODO: handle line wrapping / line height - let mut bbox = None; - for glyph in label_text - .chars() - .filter_map(|c| self.glyphs.glyphs.get(&c)) - .collect::>() - { - let glyph_dims = glyph.buffered_dimensions(); - let width = glyph_dims.0 as f32; - let height = glyph_dims.1 as f32; - - let glyph_anchor = [ - next_origin[0] + glyph.left_bearing as f32, - next_origin[1] - glyph.top_bearing as f32, - 0., - ]; - - let glyph_rect = Box2D::new( - (glyph_anchor[0], glyph_anchor[1]).into(), - (glyph_anchor[0] + width, glyph_anchor[1] + height).into(), - ); - - bbox = bbox.map_or_else( - || Some(glyph_rect), - |bbox: Box2D<_>| Some(bbox.union(&glyph_rect)), - ); - - tessellator - .tessellate_rectangle( - &glyph_rect, - &FillOptions::default(), - &mut BuffersBuilder::new( - &mut self.quad_buffer, - SymbolVertexBuilder { - glyph_anchor, - text_anchor: [origin[0], origin[1], 0.0], - texture_dimensions, - sprite_dimensions: (width, height), - sprite_offset: ( - glyph.origin_offset().0 as f32, - glyph.origin_offset().1 as f32, - ), - color: color.to_rgba8(), // TODO: is this conversion oke? - glyph: true, // Set here to true to use SDF rendering - }, - ), - ) - .ok()?; - - next_origin[0] += glyph.advance() as f32; - } - - bbox - } -} - -impl + MaxIndex> GeomProcessor -for TextTessellator -{ - fn xy(&mut self, x: f64, y: f64, _idx: usize) -> GeoResult<()> { - let new_box = Box2D::new( - Point2D::new(x as f32, y as f32), - Point2D::new(x as f32, y as f32), - ); - if let Some(bbox) = self.current_bbox { - self.current_bbox = Some(bbox.union(&new_box)) - } else { - self.current_bbox = Some(new_box) - } - Ok(()) - } - - fn point_begin(&mut self, _idx: usize) -> GeoResult<()> { - Ok(()) - } - - fn point_end(&mut self, _idx: usize) -> GeoResult<()> { - Ok(()) - } - - fn multipoint_begin(&mut self, _size: usize, _idx: usize) -> GeoResult<()> { - Ok(()) - } - - fn multipoint_end(&mut self, _idx: usize) -> GeoResult<()> { - Ok(()) - } - - fn linestring_begin(&mut self, _tagged: bool, _size: usize, _idx: usize) -> GeoResult<()> { - Ok(()) - } - - fn linestring_end(&mut self, _tagged: bool, _idx: usize) -> GeoResult<()> { - Ok(()) - } - - fn multilinestring_begin(&mut self, _size: usize, _idx: usize) -> GeoResult<()> { - Ok(()) - } - - fn multilinestring_end(&mut self, _idx: usize) -> GeoResult<()> { - Ok(()) - } - - fn polygon_begin(&mut self, _tagged: bool, _size: usize, _idx: usize) -> GeoResult<()> { - Ok(()) - } - - fn polygon_end(&mut self, _tagged: bool, _idx: usize) -> GeoResult<()> { - Ok(()) - } - - fn multipolygon_begin(&mut self, _size: usize, _idx: usize) -> GeoResult<()> { - Ok(()) - } - - fn multipolygon_end(&mut self, _idx: usize) -> GeoResult<()> { - Ok(()) - } -} - -impl + MaxIndex> PropertyProcessor -for TextTessellator -{ - fn property( - &mut self, - _idx: usize, - name: &str, - value: &ColumnValue, - ) -> geozero::error::Result { - if name == "name" { // TODO: Support different tags - match value { - ColumnValue::String(str) => { - self.current_text = Some(str.to_string()); - } - _ => {} - } - } - Ok(true) - } -} - -impl + MaxIndex> FeatureProcessor -for TextTessellator -{ - fn feature_end(&mut self, _idx: u64) -> geozero::error::Result<()> { - if let (Some(bbox), Some(text)) = (&self.current_bbox, self.current_text.clone()) { - let anchor = Anchor::Center; - // TODO: add more anchor possibilities; only support center right now - // TODO: document how anchor and glyph metrics work together to establish a baseline - let origin = match anchor { - Anchor::Center => bbox.center().to_array(), - _ => unimplemented!("no support for this anchor"), - }; - self.tessellate_glyph_quads( - origin, - text.as_str(), - Color::from_linear_rgba(1.0, 0., 0., 1.), - ); - - let next_index = self.quad_buffer.indices.len(); - let indices = (next_index - self.current_index) as u32; - self.feature_indices.push(indices); - self.current_index = next_index; - } - - self.current_bbox = None; - self.current_text = None; - Ok(()) - } -} diff --git a/maplibre/src/vector/transferables.rs b/maplibre/src/vector/transferables.rs index 94a833dff..da3cc1aed 100644 --- a/maplibre/src/vector/transferables.rs +++ b/maplibre/src/vector/transferables.rs @@ -13,7 +13,7 @@ use crate::{ vector::{AvailableVectorLayerData, MissingVectorLayerData}, }; use crate::render::shaders::SymbolVertex; -use crate::vector::AvailableSymbolVectorLayerData; +use crate::sdf::AvailableSymbolVectorLayerData; #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub enum VectorMessageTag { diff --git a/maplibre/src/vector/upload_system.rs b/maplibre/src/vector/upload_system.rs index 9c3efe6ec..115e73e98 100644 --- a/maplibre/src/vector/upload_system.rs +++ b/maplibre/src/vector/upload_system.rs @@ -17,7 +17,6 @@ use crate::{ AvailableVectorLayerData, VectorBufferPool, VectorLayerData, VectorLayersDataComponent, }, }; -use crate::vector::{AvailableSymbolVectorLayerData, SymbolBufferPool}; pub fn upload_system( MapContext { @@ -28,9 +27,9 @@ pub fn upload_system( .. }: &mut MapContext, ) { - let Some((Initialized(buffer_pool), Initialized(symbol_buffer_pool))) = world + let Some(Initialized(buffer_pool)) = world .resources - .query_mut::<(&mut Eventually, &mut Eventually)>() + .query_mut::<&mut Eventually>() else { return; }; @@ -47,14 +46,6 @@ pub fn upload_system( style, view_region, ); - upload_symbol_layer( - symbol_buffer_pool, - device, - queue, - &mut world.tiles, - style, - view_region, - ); // self.update_metadata(state, tile_repository, queue); } } @@ -154,7 +145,6 @@ fn upload_tessellated_layer( .flat_map(|data| match data { VectorLayerData::AvailableLayer(data) => Some(data), VectorLayerData::Missing(_) => None, - VectorLayerData::AvailableSymbolLayer(_) => None // TODO when to upload symbol layers? }) .filter(|data| !loaded_layers.contains(data.source_layer.as_str())) .collect::>(); @@ -203,81 +193,3 @@ fn upload_tessellated_layer( } } } - - -// TODO cleanup -fn upload_symbol_layer( - buffer_pool: &mut SymbolBufferPool, - _device: &wgpu::Device, - queue: &wgpu::Queue, - tiles: &mut Tiles, - style: &Style, - view_region: &ViewRegion, -) { - // Upload all tessellated layers which are in view - for coords in view_region.iter() { - let Some(vector_layers) = tiles.query_mut::<&VectorLayersDataComponent>(coords) else { - continue; - }; - - let loaded_layers = buffer_pool - .get_loaded_source_layers_at(coords) - .unwrap_or_default(); - - let available_layers = vector_layers - .layers - .iter() - .flat_map(|data| match data { - VectorLayerData::AvailableSymbolLayer(data) => Some(data), - VectorLayerData::Missing(_) => None, - VectorLayerData::AvailableLayer(_) => None // TODO when to upload symbol layers? - }) - .filter(|data| !loaded_layers.contains(data.source_layer.as_str())) - .collect::>(); - - for style_layer in &style.layers { - let source_layer = style_layer.source_layer.as_ref().unwrap(); // TODO: Unwrap - - let Some(AvailableSymbolVectorLayerData { - coords, - feature_indices, - buffer, - .. - }) = available_layers - .iter() - .find(|layer| source_layer.as_str() == layer.source_layer) - else { - continue; - }; - - - // Assign every feature in the layer the color from the style - let feature_metadata = (0..feature_indices.len()) // FIXME: Iterate over actual features - .enumerate() - .flat_map(|(i, _feature)| { - iter::repeat(crate::render::shaders::ShaderFeatureStyle { - color: Vec4f32::default(), - }) - .take(feature_indices[i] as usize) - }) - .collect::>(); - - // FIXME - if buffer.buffer.indices.is_empty() { - log::error!("empty indices"); - log::error!("empty indices"); - continue - } - - log::debug!("Allocating geometry at {coords}"); - buffer_pool.allocate_layer_geometry( - queue, - *coords, - style_layer.clone(), - buffer, - ShaderLayerMetadata::new(style_layer.index as f32), - &feature_metadata, - ); - } - } -} From 48dfc87bdbf6a1ea575e2042e717e26d6c939ad8 Mon Sep 17 00:00:00 2001 From: Max Ammann Date: Mon, 5 Aug 2024 12:00:56 +0100 Subject: [PATCH 07/78] Add a translucent pass --- maplibre/src/render/mod.rs | 10 +++ maplibre/src/render/render_phase/mod.rs | 23 ++++++ maplibre/src/render/translucent_pass.rs | 105 ++++++++++++++++++++++++ maplibre/src/sdf/queue_system.rs | 11 +-- maplibre/src/sdf/render_commands.rs | 7 +- maplibre/src/sdf/resource_system.rs | 2 +- 6 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 maplibre/src/render/translucent_pass.rs diff --git a/maplibre/src/render/mod.rs b/maplibre/src/render/mod.rs index 870dd6f34..7e025b93a 100644 --- a/maplibre/src/render/mod.rs +++ b/maplibre/src/render/mod.rs @@ -52,6 +52,7 @@ mod systems; // Rendering internals mod graph_runner; mod main_pass; +mod translucent_pass; pub mod shaders; // TODO: Make private // Public API @@ -75,6 +76,8 @@ use crate::{ }, window::PhysicalSize, }; +use crate::render::render_phase::TranslucentItem; +use crate::render::translucent_pass::TranslucentPassNode; pub(crate) const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType @@ -544,6 +547,7 @@ mod draw_graph { // Labels for non-input nodes pub mod node { pub const MAIN_PASS: &str = "main_pass"; + pub const TRANSLUCENT_PASS: &str = "translucent_pass"; } } @@ -573,12 +577,17 @@ impl Plugin for RenderPlugin { let mut draw_graph = RenderGraph::default(); // Draw nodes draw_graph.add_node(draw_graph::node::MAIN_PASS, MainPassNode::new()); + // Draw nodes + draw_graph.add_node(draw_graph::node::TRANSLUCENT_PASS, TranslucentPassNode::new()); // Input node let input_node_id = draw_graph.set_input(vec![]); // Edges draw_graph .add_node_edge(input_node_id, draw_graph::node::MAIN_PASS) .expect("main pass or draw node does not exist"); + draw_graph + .add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::TRANSLUCENT_PASS) + .expect("main pass or draw node does not exist"); graph.add_sub_graph(draw_graph::NAME, draw_graph); graph.add_node(main_graph::node::MAIN_PASS_DEPENDENCIES, EmptyNode); @@ -593,6 +602,7 @@ impl Plugin for RenderPlugin { // render graph dependency resources.init::>(); resources.init::>(); + resources.init::>(); // tile_view_pattern: resources.insert(Eventually::::Uninitialized); resources.init::(); diff --git a/maplibre/src/render/render_phase/mod.rs b/maplibre/src/render/render_phase/mod.rs index fe5b5c7ad..f6fcb23f6 100644 --- a/maplibre/src/render/render_phase/mod.rs +++ b/maplibre/src/render/render_phase/mod.rs @@ -68,6 +68,29 @@ impl PhaseItem for LayerItem { } } +pub struct TranslucentItem { + pub draw_function: Box>, + pub index: u32, + + pub style_layer: String, + + pub tile: Tile, + pub source_shape: TileShape, // FIXME tcs: TileShape contains buffer ranges. This is bad, move them to a component? +} + +impl PhaseItem for TranslucentItem { + type SortKey = u32; + + fn sort_key(&self) -> Self::SortKey { + self.index + } + + fn draw_function(&self) -> &dyn Draw { + self.draw_function.as_ref() + } +} + + pub struct TileMaskItem { pub draw_function: Box>, pub source_shape: TileShape, diff --git a/maplibre/src/render/translucent_pass.rs b/maplibre/src/render/translucent_pass.rs new file mode 100644 index 000000000..f20856ade --- /dev/null +++ b/maplibre/src/render/translucent_pass.rs @@ -0,0 +1,105 @@ +use std::ops::Deref; + +use wgpu::StoreOp; + +use crate::{ + render::{ + draw_graph, + graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo}, + render_phase::{RenderPhase}, + resource::TrackedRenderPass, + Eventually::Initialized, + RenderResources, + }, + tcs::world::World, +}; +use crate::render::render_phase::TranslucentItem; + +pub struct TranslucentPassNode {} + +impl TranslucentPassNode { + pub fn new() -> Self { + Self {} + } +} + +impl Node for TranslucentPassNode { + fn input(&self) -> Vec { + vec![] + } + + fn update(&mut self, _state: &mut RenderResources) {} + + fn run( + &self, + _graph: &mut RenderGraphContext, + render_context: &mut RenderContext, + state: &RenderResources, + world: &World, + ) -> Result<(), NodeRunError> { + let Initialized(render_target) = &state.render_target else { + return Ok(()); + }; + let Initialized(multisampling_texture) = &state.multisampling_texture else { + return Ok(()); + }; + + let color_attachment = if let Some(texture) = multisampling_texture { + wgpu::RenderPassColorAttachment { + view: &texture.view, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: StoreOp::Store, + }, + resolve_target: Some(render_target.deref()), + } + } else { + wgpu::RenderPassColorAttachment { + view: render_target.deref(), + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: StoreOp::Store, + }, + resolve_target: None, + } + }; + + let render_pass = + render_context + .command_encoder + .begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("translucent_pass"), + color_attachments: &[Some(color_attachment)], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + let mut tracked_pass = TrackedRenderPass::new(render_pass); + + if let Some(mask_items) = world.resources.get::>() { + log::trace!("RenderPhase::size() = {}", mask_items.size()); + for item in mask_items { + item.draw_function.draw(&mut tracked_pass, world, item); + } + } + + Ok(()) + } +} + +pub struct MainPassDriverNode; + +impl Node for MainPassDriverNode { + fn run( + &self, + graph: &mut RenderGraphContext, + _render_context: &mut RenderContext, + _resources: &RenderResources, + _world: &World, + ) -> Result<(), NodeRunError> { + graph.run_sub_graph(draw_graph::NAME, vec![])?; + + Ok(()) + } +} diff --git a/maplibre/src/sdf/queue_system.rs b/maplibre/src/sdf/queue_system.rs index 80f9cb646..ab11583d8 100644 --- a/maplibre/src/sdf/queue_system.rs +++ b/maplibre/src/sdf/queue_system.rs @@ -4,11 +4,12 @@ use crate::{ render::{ eventually::{Eventually, Eventually::Initialized}, render_commands::DrawMasks, - render_phase::{DrawState, LayerItem, RenderPhase, TileMaskItem}, + render_phase::{DrawState, RenderPhase, TileMaskItem}, tile_view_pattern::WgpuTileViewPattern, }, tcs::tiles::Tile, }; +use crate::render::render_phase::TranslucentItem; use crate::sdf::render_commands::DrawSymbols; use crate::sdf::SymbolBufferPool; @@ -17,12 +18,12 @@ pub fn queue_system(MapContext { world, .. }: &mut MapContext) { let Some(( Initialized(tile_view_pattern), mask_phase, - layer_item_phase, + translucent_phase, Initialized(symbol_buffer_pool) )) = world.resources.query_mut::<( &mut Eventually, &mut RenderPhase, - &mut RenderPhase, + &mut RenderPhase, &mut Eventually, )>() else { @@ -45,8 +46,8 @@ pub fn queue_system(MapContext { world, .. }: &mut MapContext) { if let Some(layer_entries) = symbol_buffer_pool.index().get_layers(source_shape.coords()) { for layer_entry in layer_entries { // Draw tile - layer_item_phase.add(LayerItem { - draw_function: Box::new(DrawState::::new()), + translucent_phase.add(TranslucentItem { + draw_function: Box::new(DrawState::::new()), index: layer_entry.style_layer.index, style_layer: layer_entry.style_layer.id.clone(), tile: Tile { diff --git a/maplibre/src/sdf/render_commands.rs b/maplibre/src/sdf/render_commands.rs index c0c288a51..cc06d503e 100644 --- a/maplibre/src/sdf/render_commands.rs +++ b/maplibre/src/sdf/render_commands.rs @@ -2,13 +2,14 @@ use crate::{ render::{ eventually::{Eventually, Eventually::Initialized}, - render_phase::{LayerItem, PhaseItem, RenderCommand, RenderCommandResult}, + render_phase::{PhaseItem, RenderCommand, RenderCommandResult}, resource::TrackedRenderPass, tile_view_pattern::WgpuTileViewPattern, INDEX_FORMAT, }, tcs::world::World, }; +use crate::render::render_phase::TranslucentItem; use crate::sdf::resource::GlyphTexture; use crate::sdf::{SymbolBufferPool, SymbolPipeline}; @@ -35,10 +36,10 @@ impl RenderCommand

for SetSymbolPipeline { } pub struct DrawSymbol; -impl RenderCommand for DrawSymbol { +impl RenderCommand for DrawSymbol { fn render<'w>( world: &'w World, - item: &LayerItem, + item: &TranslucentItem, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let Some((Initialized(symbol_buffer_pool), Initialized(tile_view_pattern))) = diff --git a/maplibre/src/sdf/resource_system.rs b/maplibre/src/sdf/resource_system.rs index 0d021c3c7..f5810292f 100644 --- a/maplibre/src/sdf/resource_system.rs +++ b/maplibre/src/sdf/resource_system.rs @@ -51,7 +51,7 @@ pub fn resource_system( *settings, tile_shader.describe_vertex(), tile_shader.describe_fragment(), - true, + false, false, true, // TODO ignore tile mask false, From de1378851de2158ba77c5daf347d7b64328fce78 Mon Sep 17 00:00:00 2001 From: Max Ammann Date: Mon, 5 Aug 2024 12:02:54 +0100 Subject: [PATCH 08/78] Format --- maplibre/build.rs | 6 +-- maplibre/src/debug/resource_system.rs | 2 +- maplibre/src/raster/resource_system.rs | 2 +- maplibre/src/render/mod.rs | 19 +++++---- maplibre/src/render/render_phase/mod.rs | 1 - maplibre/src/render/resource/tile_pipeline.rs | 2 +- .../src/render/systems/resource_system.rs | 2 +- maplibre/src/render/translucent_pass.rs | 8 ++-- maplibre/src/sdf/mod.rs | 40 ++++++++++++------- maplibre/src/sdf/populate_world_system.rs | 2 +- maplibre/src/sdf/queue_system.rs | 14 +++---- maplibre/src/sdf/render_commands.rs | 26 +++++------- maplibre/src/sdf/resource/glyph_texture.rs | 2 +- maplibre/src/sdf/resource_system.rs | 36 ++++++++--------- maplibre/src/sdf/tessellation.rs | 30 +++++++------- maplibre/src/sdf/text.rs | 7 ++-- maplibre/src/sdf/upload_system.rs | 25 ++++++------ maplibre/src/style/layer.rs | 8 ++-- maplibre/src/style/mod.rs | 10 ++--- maplibre/src/vector/mod.rs | 15 ++++--- maplibre/src/vector/process_vector.rs | 29 +++++++------- maplibre/src/vector/queue_system.rs | 3 +- maplibre/src/vector/request_system.rs | 8 ++-- maplibre/src/vector/resource/mod.rs | 1 - maplibre/src/vector/resource_system.rs | 8 ++-- maplibre/src/vector/tessellation.rs | 29 ++++++-------- maplibre/src/vector/transferables.rs | 12 +++--- 27 files changed, 173 insertions(+), 174 deletions(-) diff --git a/maplibre/build.rs b/maplibre/build.rs index 5e4208470..9d52c7d50 100644 --- a/maplibre/build.rs +++ b/maplibre/build.rs @@ -3,8 +3,8 @@ //! This script is built and executed just before building the package. //! It will validate the WGSL (WebGPU Shading Language) shaders and embed static files. -use std::fs; -use std::path::PathBuf; +use std::{fs, path::PathBuf}; + use maplibre_build_tools::wgsl::validate_project_wgsl; #[cfg(feature = "embed-static-tiles")] @@ -65,11 +65,9 @@ fn generate_protobuf() { }) .collect::>(); - prost_build::compile_protos(&proto_paths, &[PathBuf::from("./proto/")]).unwrap(); } - fn main() { validate_project_wgsl(); diff --git a/maplibre/src/debug/resource_system.rs b/maplibre/src/debug/resource_system.rs index 016f8a30c..484ed6cf8 100644 --- a/maplibre/src/debug/resource_system.rs +++ b/maplibre/src/debug/resource_system.rs @@ -49,7 +49,7 @@ pub fn resource_system( false, false, false, - false + false, ) .describe_render_pipeline() .initialize(device); diff --git a/maplibre/src/raster/resource_system.rs b/maplibre/src/raster/resource_system.rs index 22189b08d..427228393 100644 --- a/maplibre/src/raster/resource_system.rs +++ b/maplibre/src/raster/resource_system.rs @@ -51,7 +51,7 @@ pub fn resource_system( false, surface.is_multisampling_supported(settings.msaa), true, - false + false, ) .describe_render_pipeline() .initialize(device), diff --git a/maplibre/src/render/mod.rs b/maplibre/src/render/mod.rs index 7e025b93a..bff2f955a 100644 --- a/maplibre/src/render/mod.rs +++ b/maplibre/src/render/mod.rs @@ -52,8 +52,8 @@ mod systems; // Rendering internals mod graph_runner; mod main_pass; -mod translucent_pass; -pub mod shaders; // TODO: Make private +pub mod shaders; +mod translucent_pass; // TODO: Make private // Public API pub mod builder; @@ -70,14 +70,13 @@ pub use shaders::ShaderVertex; use crate::{ render::{ - render_phase::{LayerItem, RenderPhase, TileMaskItem}, + render_phase::{LayerItem, RenderPhase, TileMaskItem, TranslucentItem}, systems::{graph_runner_system::GraphRunnerSystem, upload_system::upload_system}, tile_view_pattern::{ViewTileSources, WgpuTileViewPattern}, + translucent_pass::TranslucentPassNode, }, window::PhysicalSize, }; -use crate::render::render_phase::TranslucentItem; -use crate::render::translucent_pass::TranslucentPassNode; pub(crate) const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType @@ -578,7 +577,10 @@ impl Plugin for RenderPlugin { // Draw nodes draw_graph.add_node(draw_graph::node::MAIN_PASS, MainPassNode::new()); // Draw nodes - draw_graph.add_node(draw_graph::node::TRANSLUCENT_PASS, TranslucentPassNode::new()); + draw_graph.add_node( + draw_graph::node::TRANSLUCENT_PASS, + TranslucentPassNode::new(), + ); // Input node let input_node_id = draw_graph.set_input(vec![]); // Edges @@ -586,7 +588,10 @@ impl Plugin for RenderPlugin { .add_node_edge(input_node_id, draw_graph::node::MAIN_PASS) .expect("main pass or draw node does not exist"); draw_graph - .add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::TRANSLUCENT_PASS) + .add_node_edge( + draw_graph::node::MAIN_PASS, + draw_graph::node::TRANSLUCENT_PASS, + ) .expect("main pass or draw node does not exist"); graph.add_sub_graph(draw_graph::NAME, draw_graph); diff --git a/maplibre/src/render/render_phase/mod.rs b/maplibre/src/render/render_phase/mod.rs index f6fcb23f6..24275601e 100644 --- a/maplibre/src/render/render_phase/mod.rs +++ b/maplibre/src/render/render_phase/mod.rs @@ -90,7 +90,6 @@ impl PhaseItem for TranslucentItem { } } - pub struct TileMaskItem { pub draw_function: Box>, pub source_shape: TileShape, diff --git a/maplibre/src/render/resource/tile_pipeline.rs b/maplibre/src/render/resource/tile_pipeline.rs index e7c560b61..03c6ef50a 100644 --- a/maplibre/src/render/resource/tile_pipeline.rs +++ b/maplibre/src/render/resource/tile_pipeline.rs @@ -37,7 +37,7 @@ impl TilePipeline { wireframe: bool, multisampling: bool, raster: bool, - glyph_rendering: bool + glyph_rendering: bool, ) -> Self { TilePipeline { name, diff --git a/maplibre/src/render/systems/resource_system.rs b/maplibre/src/render/systems/resource_system.rs index 4f82763b1..7703874b0 100644 --- a/maplibre/src/render/systems/resource_system.rs +++ b/maplibre/src/render/systems/resource_system.rs @@ -128,7 +128,7 @@ impl System for ResourceSystem { false, surface.is_multisampling_supported(settings.msaa), false, - false + false, ) .describe_render_pipeline() .initialize(device); diff --git a/maplibre/src/render/translucent_pass.rs b/maplibre/src/render/translucent_pass.rs index f20856ade..738f361e7 100644 --- a/maplibre/src/render/translucent_pass.rs +++ b/maplibre/src/render/translucent_pass.rs @@ -6,14 +6,13 @@ use crate::{ render::{ draw_graph, graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo}, - render_phase::{RenderPhase}, + render_phase::{RenderPhase, TranslucentItem}, resource::TrackedRenderPass, Eventually::Initialized, RenderResources, }, tcs::world::World, }; -use crate::render::render_phase::TranslucentItem; pub struct TranslucentPassNode {} @@ -78,7 +77,10 @@ impl Node for TranslucentPassNode { let mut tracked_pass = TrackedRenderPass::new(render_pass); if let Some(mask_items) = world.resources.get::>() { - log::trace!("RenderPhase::size() = {}", mask_items.size()); + log::trace!( + "RenderPhase::size() = {}", + mask_items.size() + ); for item in mask_items { item.draw_function.draw(&mut tracked_pass, world, item); } diff --git a/maplibre/src/sdf/mod.rs b/maplibre/src/sdf/mod.rs index 605fc76a1..7b3ca6e2b 100644 --- a/maplibre/src/sdf/mod.rs +++ b/maplibre/src/sdf/mod.rs @@ -7,19 +7,19 @@ use crate::{ plugin::Plugin, render::{ eventually::Eventually, - shaders::{ShaderFeatureStyle, ShaderLayerMetadata} + graph::RenderGraph, + shaders::{ShaderFeatureStyle, ShaderLayerMetadata, SymbolVertex}, + RenderStageLabel, }, schedule::Schedule, - tcs::{tiles::TileComponent, world::World}, - vector::resource::BufferPool, - vector::tessellation::{IndexDataType, OverAlignedVertexBuffer}, + sdf::resource::GlyphTexture, + tcs::{system::SystemContainer, tiles::TileComponent, world::World}, + vector::{ + resource::BufferPool, + tessellation::{IndexDataType, OverAlignedVertexBuffer}, + VectorTransferables, + }, }; -use crate::render::graph::RenderGraph; -use crate::render::RenderStageLabel; -use crate::render::shaders::SymbolVertex; -use crate::sdf::resource::GlyphTexture; -use crate::tcs::system::SystemContainer; -use crate::vector::VectorTransferables; mod populate_world_system; mod queue_system; @@ -76,16 +76,26 @@ impl Plugin for SdfPlugin { schedule.add_system_to_stage( RenderStageLabel::Extract, - SystemContainer::new(crate::sdf::populate_world_system::PopulateWorldSystem::::new(&kernel)), + SystemContainer::new( + crate::sdf::populate_world_system::PopulateWorldSystem::::new(&kernel), + ), ); - schedule.add_system_to_stage(RenderStageLabel::Prepare, crate::sdf::resource_system::resource_system); - schedule.add_system_to_stage(RenderStageLabel::Queue, crate::sdf::upload_system::upload_system); // FIXME tcs: Upload updates the TileView in tileviewpattern -> upload most run before prepare - schedule.add_system_to_stage(RenderStageLabel::Queue, crate::sdf::queue_system::queue_system); + schedule.add_system_to_stage( + RenderStageLabel::Prepare, + crate::sdf::resource_system::resource_system, + ); + schedule.add_system_to_stage( + RenderStageLabel::Queue, + crate::sdf::upload_system::upload_system, + ); // FIXME tcs: Upload updates the TileView in tileviewpattern -> upload most run before prepare + schedule.add_system_to_stage( + RenderStageLabel::Queue, + crate::sdf::queue_system::queue_system, + ); } } - pub struct AvailableSymbolVectorLayerData { pub coords: WorldTileCoords, pub source_layer: String, diff --git a/maplibre/src/sdf/populate_world_system.rs b/maplibre/src/sdf/populate_world_system.rs index 7293baec1..1f4cf3eb4 100644 --- a/maplibre/src/sdf/populate_world_system.rs +++ b/maplibre/src/sdf/populate_world_system.rs @@ -5,10 +5,10 @@ use crate::{ environment::Environment, io::apc::{AsyncProcedureCall, Message}, kernel::Kernel, + sdf::{SymbolLayerData, SymbolLayersDataComponent}, tcs::system::System, vector::transferables::*, }; -use crate::sdf::{SymbolLayerData, SymbolLayersDataComponent}; pub struct PopulateWorldSystem { kernel: Rc>, diff --git a/maplibre/src/sdf/queue_system.rs b/maplibre/src/sdf/queue_system.rs index ab11583d8..021e7802a 100644 --- a/maplibre/src/sdf/queue_system.rs +++ b/maplibre/src/sdf/queue_system.rs @@ -4,22 +4,19 @@ use crate::{ render::{ eventually::{Eventually, Eventually::Initialized}, render_commands::DrawMasks, - render_phase::{DrawState, RenderPhase, TileMaskItem}, + render_phase::{DrawState, RenderPhase, TileMaskItem, TranslucentItem}, tile_view_pattern::WgpuTileViewPattern, }, + sdf::{render_commands::DrawSymbols, SymbolBufferPool}, tcs::tiles::Tile, }; -use crate::render::render_phase::TranslucentItem; -use crate::sdf::render_commands::DrawSymbols; -use crate::sdf::SymbolBufferPool; - pub fn queue_system(MapContext { world, .. }: &mut MapContext) { let Some(( Initialized(tile_view_pattern), mask_phase, translucent_phase, - Initialized(symbol_buffer_pool) + Initialized(symbol_buffer_pool), )) = world.resources.query_mut::<( &mut Eventually, &mut RenderPhase, @@ -30,7 +27,6 @@ pub fn queue_system(MapContext { world, .. }: &mut MapContext) { return; }; - for view_tile in tile_view_pattern.iter() { let coords = &view_tile.coords(); tracing::trace!("Drawing tile at {coords}"); @@ -43,7 +39,9 @@ pub fn queue_system(MapContext { world, .. }: &mut MapContext) { source_shape: source_shape.clone(), }); - if let Some(layer_entries) = symbol_buffer_pool.index().get_layers(source_shape.coords()) { + if let Some(layer_entries) = + symbol_buffer_pool.index().get_layers(source_shape.coords()) + { for layer_entry in layer_entries { // Draw tile translucent_phase.add(TranslucentItem { diff --git a/maplibre/src/sdf/render_commands.rs b/maplibre/src/sdf/render_commands.rs index cc06d503e..4c528e079 100644 --- a/maplibre/src/sdf/render_commands.rs +++ b/maplibre/src/sdf/render_commands.rs @@ -1,17 +1,14 @@ - use crate::{ render::{ eventually::{Eventually, Eventually::Initialized}, - render_phase::{PhaseItem, RenderCommand, RenderCommandResult}, + render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TranslucentItem}, resource::TrackedRenderPass, tile_view_pattern::WgpuTileViewPattern, INDEX_FORMAT, }, + sdf::{resource::GlyphTexture, SymbolBufferPool, SymbolPipeline}, tcs::world::World, }; -use crate::render::render_phase::TranslucentItem; -use crate::sdf::resource::GlyphTexture; -use crate::sdf::{SymbolBufferPool, SymbolPipeline}; pub struct SetSymbolPipeline; impl RenderCommand

for SetSymbolPipeline { @@ -21,10 +18,9 @@ impl RenderCommand

for SetSymbolPipeline { pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let Some((Initialized(GlyphTexture { ref bind_group, .. }), Initialized(symbol_pipeline))) = - world.resources.query::<( - &Eventually, - &Eventually, - )>() + world + .resources + .query::<(&Eventually, &Eventually)>() else { return RenderCommandResult::Failure; }; @@ -87,9 +83,7 @@ impl RenderCommand for DrawSymbol { pass.set_stencil_reference(reference); pass.set_index_buffer( - symbol_buffer_pool - .indices() - .slice(index_range), + symbol_buffer_pool.indices().slice(index_range), INDEX_FORMAT, ); pass.set_vertex_buffer( @@ -98,7 +92,10 @@ impl RenderCommand for DrawSymbol { .vertices() .slice(entry.vertices_buffer_range()), ); - pass.set_vertex_buffer(1, tile_view_pattern.buffer().slice(tile_view_pattern_buffer)); + pass.set_vertex_buffer( + 1, + tile_view_pattern.buffer().slice(tile_view_pattern_buffer), + ); pass.set_vertex_buffer( 2, symbol_buffer_pool @@ -111,5 +108,4 @@ impl RenderCommand for DrawSymbol { } } - -pub type DrawSymbols = (SetSymbolPipeline, DrawSymbol); \ No newline at end of file +pub type DrawSymbols = (SetSymbolPipeline, DrawSymbol); diff --git a/maplibre/src/sdf/resource/glyph_texture.rs b/maplibre/src/sdf/resource/glyph_texture.rs index 51ddc200d..04a448cba 100644 --- a/maplibre/src/sdf/resource/glyph_texture.rs +++ b/maplibre/src/sdf/resource/glyph_texture.rs @@ -36,4 +36,4 @@ impl GlyphTexture { }); Self { bind_group } } -} \ No newline at end of file +} diff --git a/maplibre/src/sdf/resource_system.rs b/maplibre/src/sdf/resource_system.rs index f5810292f..fb375eefe 100644 --- a/maplibre/src/sdf/resource_system.rs +++ b/maplibre/src/sdf/resource_system.rs @@ -1,5 +1,6 @@ //! Prepares GPU-owned resources by initializing them if they are uninitialized or out-of-date. use wgpu::util::{DeviceExt, TextureDataOrder}; + use crate::{ context::MapContext, render::{ @@ -9,12 +10,9 @@ use crate::{ shaders::Shader, RenderResources, Renderer, }, + sdf::{resource::GlyphTexture, text::GlyphSet, SymbolBufferPool, SymbolPipeline}, + vector::resource::BufferPool, }; -use crate::sdf::{SymbolBufferPool, SymbolPipeline}; -use crate::sdf::resource::GlyphTexture; -use crate::sdf::text::GlyphSet; -use crate::vector::resource::BufferPool; - pub fn resource_system( MapContext { @@ -30,12 +28,18 @@ pub fn resource_system( .. }: &mut MapContext, ) { - let Some((symbol_buffer_pool, symbol_pipeline, glyph_texture_sampler, glyph_texture_bind_group)) = world.resources.query_mut::<( + let Some(( + symbol_buffer_pool, + symbol_pipeline, + glyph_texture_sampler, + glyph_texture_bind_group, + )) = world.resources.query_mut::<( &mut Eventually, &mut Eventually, &mut Eventually<(wgpu::Texture, wgpu::Sampler)>, &mut Eventually, - )>() else { + )>() + else { return; }; @@ -57,17 +61,14 @@ pub fn resource_system( false, surface.is_multisampling_supported(settings.msaa), false, - true + true, ) - .describe_render_pipeline() - .initialize(device); - + .describe_render_pipeline() + .initialize(device); let (texture, sampler) = glyph_texture_sampler.initialize(|| { let data = std::fs::read("./data/0-255.pbf").unwrap(); - let glyphs = GlyphSet::try_from( - data.as_slice(), - ).unwrap(); + let glyphs = GlyphSet::try_from(data.as_slice()).unwrap(); let (width, height) = glyphs.get_texture_dimensions(); @@ -102,12 +103,7 @@ pub fn resource_system( }); glyph_texture_bind_group.initialize(|| { - GlyphTexture::from_device( - device, - texture, - sampler, - &pipeline.get_bind_group_layout(0), - ) + GlyphTexture::from_device(device, texture, sampler, &pipeline.get_bind_group_layout(0)) }); SymbolPipeline(pipeline) diff --git a/maplibre/src/sdf/tessellation.rs b/maplibre/src/sdf/tessellation.rs index c2779eb23..dfae22717 100644 --- a/maplibre/src/sdf/tessellation.rs +++ b/maplibre/src/sdf/tessellation.rs @@ -3,17 +3,18 @@ use std::fs; use csscolorparser::Color; -use geozero::{FeatureProcessor, GeomProcessor, PropertyProcessor}; -use geozero::ColumnValue; -use lyon::geom::{Box2D, euclid::Point2D}; -use lyon::tessellation::{ - BuffersBuilder, FillOptions, FillTessellator, geometry_builder::MaxIndex - , +use geozero::{ColumnValue, FeatureProcessor, GeomProcessor, PropertyProcessor}; +use lyon::{ + geom::{euclid::Point2D, Box2D}, + tessellation::{ + geometry_builder::MaxIndex, BuffersBuilder, FillOptions, FillTessellator, VertexBuffers, + }, }; -use lyon::tessellation::VertexBuffers; -use crate::render::shaders::SymbolVertex; -use crate::sdf::text::{Anchor, GlyphSet, SymbolVertexBuilder}; +use crate::{ + render::shaders::SymbolVertex, + sdf::text::{Anchor, GlyphSet, SymbolVertexBuilder}, +}; const DEFAULT_TOLERANCE: f32 = 0.02; @@ -37,7 +38,7 @@ pub struct TextTessellator } impl + MaxIndex> Default -for TextTessellator + for TextTessellator { fn default() -> Self { let data = fs::read("./data/0-255.pbf").unwrap(); @@ -125,7 +126,7 @@ impl + MaxIndex> TextTesse } impl + MaxIndex> GeomProcessor -for TextTessellator + for TextTessellator { fn xy(&mut self, x: f64, y: f64, _idx: usize) -> GeoResult<()> { let new_box = Box2D::new( @@ -190,7 +191,7 @@ for TextTessellator } impl + MaxIndex> PropertyProcessor -for TextTessellator + for TextTessellator { fn property( &mut self, @@ -198,7 +199,8 @@ for TextTessellator name: &str, value: &ColumnValue, ) -> geozero::error::Result { - if name == "name" { // TODO: Support different tags + if name == "name" { + // TODO: Support different tags match value { ColumnValue::String(str) => { self.current_text = Some(str.to_string()); @@ -211,7 +213,7 @@ for TextTessellator } impl + MaxIndex> FeatureProcessor -for TextTessellator + for TextTessellator { fn feature_end(&mut self, _idx: u64) -> geozero::error::Result<()> { if let (Some(bbox), Some(text)) = (&self.current_bbox, self.current_text.clone()) { diff --git a/maplibre/src/sdf/text.rs b/maplibre/src/sdf/text.rs index f159f93e6..db91f98ab 100644 --- a/maplibre/src/sdf/text.rs +++ b/maplibre/src/sdf/text.rs @@ -3,6 +3,7 @@ use std::{collections::BTreeMap, convert::TryFrom}; use image::{GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma}; use lyon::tessellation::{FillVertex, FillVertexConstructor}; use prost::{DecodeError, Message}; + use crate::render::shaders::SymbolVertex; pub mod sdf_glyphs { @@ -158,10 +159,10 @@ impl FillVertexConstructor for SymbolVertexBuilder { let tex_coords = [ x_offset + ((vertex_position.x - self.glyph_anchor[0]) / self.sprite_dimensions.0) - * sprite_ratio_x, + * sprite_ratio_x, y_offset + ((vertex_position.y - self.glyph_anchor[1]) / self.sprite_dimensions.1) - * sprite_ratio_y, + * sprite_ratio_y, ]; SymbolVertex { @@ -185,4 +186,4 @@ pub enum Anchor { TopRight, BottomLeft, BottomRight, -} \ No newline at end of file +} diff --git a/maplibre/src/sdf/upload_system.rs b/maplibre/src/sdf/upload_system.rs index b8a7bfd62..65cfc47fe 100644 --- a/maplibre/src/sdf/upload_system.rs +++ b/maplibre/src/sdf/upload_system.rs @@ -7,15 +7,17 @@ use crate::{ coords::ViewRegion, render::{ eventually::{Eventually, Eventually::Initialized}, - shaders::{Vec4f32}, + shaders::{ShaderLayerMetadata, Vec4f32}, tile_view_pattern::DEFAULT_TILE_SIZE, Renderer, }, + sdf::{ + AvailableSymbolVectorLayerData, SymbolBufferPool, SymbolLayerData, + SymbolLayersDataComponent, + }, style::Style, tcs::tiles::Tiles, }; -use crate::render::shaders::ShaderLayerMetadata; -use crate::sdf::{AvailableSymbolVectorLayerData, SymbolBufferPool, SymbolLayerData, SymbolLayersDataComponent}; pub fn upload_system( MapContext { @@ -81,18 +83,17 @@ fn upload_symbol_layer( let source_layer = style_layer.source_layer.as_ref().unwrap(); // TODO: Unwrap let Some(AvailableSymbolVectorLayerData { - coords, - feature_indices, - buffer, - .. - }) = available_layers + coords, + feature_indices, + buffer, + .. + }) = available_layers .iter() .find(|layer| source_layer.as_str() == layer.source_layer) else { continue; }; - // Assign every feature in the layer the color from the style let feature_metadata = (0..feature_indices.len()) // FIXME: Iterate over actual features .enumerate() @@ -100,15 +101,15 @@ fn upload_symbol_layer( iter::repeat(crate::render::shaders::ShaderFeatureStyle { color: Vec4f32::default(), }) - .take(feature_indices[i] as usize) + .take(feature_indices[i] as usize) }) .collect::>(); // FIXME - if buffer.buffer.indices.is_empty() { + if buffer.buffer.indices.is_empty() { log::error!("empty indices"); log::error!("empty indices"); - continue + continue; } log::debug!("Allocating geometry at {coords}"); diff --git a/maplibre/src/style/layer.rs b/maplibre/src/style/layer.rs index f0e9fb540..33e99a0c1 100644 --- a/maplibre/src/style/layer.rs +++ b/maplibre/src/style/layer.rs @@ -1,7 +1,9 @@ //! Vector tile layer drawing utilities. -use std::collections::HashMap; -use std::hash::{Hash, Hasher}; +use std::{ + collections::HashMap, + hash::{Hash, Hasher}, +}; use cint::{Alpha, EncodedSrgb}; use csscolorparser::Color; @@ -87,7 +89,6 @@ pub struct SymbolPaint { // TODO a lot } - /// The different types of paints. #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "type", content = "paint")] @@ -169,4 +170,3 @@ impl Default for StyleLayer { } } } - diff --git a/maplibre/src/style/mod.rs b/maplibre/src/style/mod.rs index e373cafa4..259caf033 100644 --- a/maplibre/src/style/mod.rs +++ b/maplibre/src/style/mod.rs @@ -1,22 +1,19 @@ //! Vector tile format styling. -pub use cint::*; use std::{collections::HashMap, str::FromStr}; +pub use cint::*; use csscolorparser::Color; use serde::{Deserialize, Serialize}; use crate::style::{ - layer::{FillPaint, LayerPaint, LinePaint, StyleLayer}, - layer::RasterPaint, + layer::{FillPaint, LayerPaint, LinePaint, RasterPaint, StyleLayer, SymbolPaint}, source::Source, }; -use crate::style::layer::SymbolPaint; pub mod layer; pub mod source; - /// Stores the style for a multi-layered map. #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Style { @@ -153,8 +150,7 @@ impl Default for Style { maxzoom: None, minzoom: None, metadata: None, - paint: Some(LayerPaint::Symbol(SymbolPaint { - })), + paint: Some(LayerPaint::Symbol(SymbolPaint {})), source: None, source_layer: Some("transportation_name".to_string()), }, diff --git a/maplibre/src/vector/mod.rs b/maplibre/src/vector/mod.rs index aea3f2fd8..442dcaba3 100644 --- a/maplibre/src/vector/mod.rs +++ b/maplibre/src/vector/mod.rs @@ -13,20 +13,23 @@ use crate::{ plugin::Plugin, render::{ eventually::Eventually, - RenderStageLabel, + graph::RenderGraph, shaders::{ShaderFeatureStyle, ShaderLayerMetadata}, - ShaderVertex, tile_view_pattern::{HasTile, ViewTileSources}, + tile_view_pattern::{HasTile, ViewTileSources}, + RenderStageLabel, ShaderVertex, }, schedule::Schedule, tcs::{system::SystemContainer, tiles::TileComponent, world::World}, vector::{ - populate_world_system::PopulateWorldSystem, queue_system::queue_system, - request_system::RequestSystem, resource::BufferPool, resource_system::resource_system, + populate_world_system::PopulateWorldSystem, + queue_system::queue_system, + request_system::RequestSystem, + resource::BufferPool, + resource_system::resource_system, + tessellation::{IndexDataType, OverAlignedVertexBuffer}, upload_system::upload_system, }, - vector::tessellation::{IndexDataType, OverAlignedVertexBuffer}, }; -use crate::render::graph::RenderGraph; mod populate_world_system; mod process_vector; diff --git a/maplibre/src/vector/process_vector.rs b/maplibre/src/vector/process_vector.rs index ed5c0b2d3..dec027eb5 100644 --- a/maplibre/src/vector/process_vector.rs +++ b/maplibre/src/vector/process_vector.rs @@ -12,16 +12,17 @@ use crate::{ apc::{Context, SendError}, geometry_index::{IndexProcessor, IndexedGeometry, TileIndex}, }, - render::ShaderVertex, - vector::tessellation::{ZeroTessellator, IndexDataType, OverAlignedVertexBuffer}, - vector::transferables::{ - LayerIndexed, LayerMissing, LayerTessellated, TileTessellated, VectorTransferables, + render::{shaders::SymbolVertex, ShaderVertex}, + sdf::tessellation::TextTessellator, + style::layer::{LayerPaint, StyleLayer}, + vector::{ + tessellation::{IndexDataType, OverAlignedVertexBuffer, ZeroTessellator}, + transferables::{ + LayerIndexed, LayerMissing, LayerTessellated, SymbolLayerTessellated, TileTessellated, + VectorTransferables, + }, }, }; -use crate::render::shaders::SymbolVertex; -use crate::sdf::tessellation::TextTessellator; -use crate::style::layer::{LayerPaint, StyleLayer}; -use crate::vector::transferables::SymbolLayerTessellated; #[derive(Error, Debug)] pub enum ProcessVectorError { @@ -53,8 +54,11 @@ pub fn process_vector_tile( for style_layer in &tile_request.layers { let id = &style_layer.id; if let (Some(paint), Some(source_layer)) = (&style_layer.paint, &style_layer.source_layer) { - - if let Some(layer) = tile.layers.iter_mut().find(|layer| &layer.name == source_layer) { + if let Some(layer) = tile + .layers + .iter_mut() + .find(|layer| &layer.name == source_layer) + { let original_layer = layer.clone(); match paint { @@ -75,7 +79,6 @@ pub fn process_vector_tile( } } LayerPaint::Symbol(_) => { - let mut tessellator = TextTessellator::::default(); if let Err(e) = layer.process(&mut tessellator) { @@ -85,7 +88,7 @@ pub fn process_vector_tile( } else { if tessellator.quad_buffer.indices.is_empty() { log::error!("quad buffer empty"); - continue + continue; } context.symbol_layer_tesselation_finished( coords, @@ -94,7 +97,6 @@ pub fn process_vector_tile( original_layer, )?; } - } _ => { log::warn!("unhandled style layer type in {id}"); @@ -206,7 +208,6 @@ impl ProcessVectorContext { .map_err(|e| ProcessVectorError::SendError(e)) } - fn layer_indexing_finished( &mut self, coords: &WorldTileCoords, diff --git a/maplibre/src/vector/queue_system.rs b/maplibre/src/vector/queue_system.rs index 8140ef796..be0a87853 100644 --- a/maplibre/src/vector/queue_system.rs +++ b/maplibre/src/vector/queue_system.rs @@ -43,7 +43,7 @@ pub fn queue_system(MapContext { world, .. }: &mut MapContext) { if let Some(layer_entries) = buffer_pool_index.get_layers(source_shape.coords()) { for layer_entry in layer_entries { - // continue; + // continue; // Draw tile layer_item_phase.add(LayerItem { draw_function: Box::new(DrawState::::new()), @@ -56,7 +56,6 @@ pub fn queue_system(MapContext { world, .. }: &mut MapContext) { }); } }; - }); } } diff --git a/maplibre/src/vector/request_system.rs b/maplibre/src/vector/request_system.rs index df41ff276..7ef18f8b7 100644 --- a/maplibre/src/vector/request_system.rs +++ b/maplibre/src/vector/request_system.rs @@ -11,6 +11,8 @@ use crate::{ }, kernel::Kernel, render::tile_view_pattern::DEFAULT_TILE_SIZE, + sdf::SymbolLayersDataComponent, + style::layer::StyleLayer, tcs::system::System, vector::{ process_vector::{process_vector_tile, ProcessVectorContext, VectorTileRequest}, @@ -18,8 +20,6 @@ use crate::{ VectorLayersDataComponent, }, }; -use crate::sdf::SymbolLayersDataComponent; -use crate::style::layer::StyleLayer; pub struct RequestSystem { kernel: Rc>, @@ -115,9 +115,7 @@ pub fn fetch_vector_apc = style - .layers.iter().cloned() - .collect(); + let requested_layers: HashSet = style.layers.iter().cloned().collect(); let client = kernel.source_client(); diff --git a/maplibre/src/vector/resource/mod.rs b/maplibre/src/vector/resource/mod.rs index 99242c462..5d22b8dfa 100644 --- a/maplibre/src/vector/resource/mod.rs +++ b/maplibre/src/vector/resource/mod.rs @@ -1,4 +1,3 @@ pub use buffer_pool::*; mod buffer_pool; - diff --git a/maplibre/src/vector/resource_system.rs b/maplibre/src/vector/resource_system.rs index c71680614..f6080853b 100644 --- a/maplibre/src/vector/resource_system.rs +++ b/maplibre/src/vector/resource_system.rs @@ -3,10 +3,10 @@ use crate::{ context::MapContext, render::{ eventually::Eventually, - Renderer, - RenderResources, resource::{RenderPipeline, TilePipeline}, - shaders, shaders::Shader, + shaders, + shaders::Shader, + RenderResources, Renderer, }, vector::{resource::BufferPool, VectorBufferPool, VectorPipeline}, }; @@ -50,7 +50,7 @@ pub fn resource_system( false, surface.is_multisampling_supported(settings.msaa), false, - false + false, ) .describe_render_pipeline() .initialize(device); diff --git a/maplibre/src/vector/tessellation.rs b/maplibre/src/vector/tessellation.rs index 8621d7bae..f16412049 100644 --- a/maplibre/src/vector/tessellation.rs +++ b/maplibre/src/vector/tessellation.rs @@ -2,24 +2,19 @@ use std::cell::RefCell; +use bytemuck::Pod; use geozero::{FeatureProcessor, GeomProcessor, PropertyProcessor}; use lyon::{ geom, path::{path::Builder, Path}, tessellation::{ geometry_builder::MaxIndex, BuffersBuilder, FillOptions, FillRule, FillTessellator, - StrokeOptions, StrokeTessellator, + FillVertex, FillVertexConstructor, StrokeOptions, StrokeTessellator, StrokeVertex, + StrokeVertexConstructor, VertexBuffers, }, }; -use crate::{ - render::ShaderVertex, -}; - -use bytemuck::Pod; -use lyon::tessellation::{ - FillVertex, FillVertexConstructor, StrokeVertex, StrokeVertexConstructor, VertexBuffers, -}; +use crate::render::ShaderVertex; const DEFAULT_TOLERANCE: f32 = 0.02; @@ -63,8 +58,8 @@ impl OverAlignedVertexBuffer { pub fn from_iters(vertices: IV, indices: II, usable_indices: u32) -> Self where - IV: IntoIterator, - II: IntoIterator, + IV: IntoIterator, + II: IntoIterator, IV::IntoIter: ExactSizeIterator, II::IntoIter: ExactSizeIterator, { @@ -138,7 +133,7 @@ pub struct ZeroTessellator } impl + MaxIndex> Default -for ZeroTessellator + for ZeroTessellator { fn default() -> Self { Self { @@ -195,7 +190,7 @@ impl + MaxIndex> ZeroTesse } impl + MaxIndex> GeomProcessor -for ZeroTessellator + for ZeroTessellator { fn xy(&mut self, x: f64, y: f64, _idx: usize) -> GeoResult<()> { // log::info!("xy"); @@ -293,15 +288,15 @@ for ZeroTessellator } impl + MaxIndex> PropertyProcessor -for ZeroTessellator -{} + for ZeroTessellator +{ +} impl + MaxIndex> FeatureProcessor -for ZeroTessellator + for ZeroTessellator { fn feature_end(&mut self, _idx: u64) -> geozero::error::Result<()> { self.update_feature_indices(); Ok(()) } } - diff --git a/maplibre/src/vector/transferables.rs b/maplibre/src/vector/transferables.rs index da3cc1aed..19eeaf0a0 100644 --- a/maplibre/src/vector/transferables.rs +++ b/maplibre/src/vector/transferables.rs @@ -8,12 +8,13 @@ use crate::{ apc::{IntoMessage, Message, MessageTag}, geometry_index::TileIndex, }, - render::ShaderVertex, - vector::tessellation::{IndexDataType, OverAlignedVertexBuffer}, - vector::{AvailableVectorLayerData, MissingVectorLayerData}, + render::{shaders::SymbolVertex, ShaderVertex}, + sdf::AvailableSymbolVectorLayerData, + vector::{ + tessellation::{IndexDataType, OverAlignedVertexBuffer}, + AvailableVectorLayerData, MissingVectorLayerData, + }, }; -use crate::render::shaders::SymbolVertex; -use crate::sdf::AvailableSymbolVectorLayerData; #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub enum VectorMessageTag { @@ -234,7 +235,6 @@ impl LayerTessellated for DefaultLayerTessellated { } } - #[derive(Clone)] pub struct DefaultSymbolLayerTessellated { pub coords: WorldTileCoords, From b213f6db488a64f36f0cc3dec48f3a77e4e6c200 Mon Sep 17 00:00:00 2001 From: Max Ammann Date: Mon, 5 Aug 2024 17:13:40 +0100 Subject: [PATCH 09/78] Disable fragment discarding --- maplibre/src/render/shaders/sdf.fragment.wgsl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/maplibre/src/render/shaders/sdf.fragment.wgsl b/maplibre/src/render/shaders/sdf.fragment.wgsl index a17511a62..9c49ee942 100644 --- a/maplibre/src/render/shaders/sdf.fragment.wgsl +++ b/maplibre/src/render/shaders/sdf.fragment.wgsl @@ -37,16 +37,15 @@ fn main(in: VertexOutput) -> Output { let color_rgb = mix(outline_color.rgb, in.color.rgb, border); + // The translucent pass does not have a depth buffer. Therefore we do not need to discord the fragments: // "Another Good Trick" from https://www.sjbaker.org/steve/omniv/alpha_sorting.html // Using discard is an alternative for GL_ALPHA_TEST. // https://stackoverflow.com/questions/53024693/opengl-is-discard-the-only-replacement-for-deprecated-gl-alpha-test - // Alternative is to disable the depth buffer for the RenderPass using sdf.fragment.wgsl - if (alpha == 0.0) { - discard; - } + // if (alpha == 0.0) { + // discard; + // } return Output(vec4(color_rgb, in.color.a * alpha)); - //return Output(vec4(vec3(0.0, 0.0, 0.0), 1.0)); } From b8458e8aaa21538d157685d83662f0ea657bf051 Mon Sep 17 00:00:00 2001 From: Max Ammann Date: Mon, 5 Aug 2024 19:02:25 +0100 Subject: [PATCH 10/78] Fix rendering of labels and also handle eg spaces --- .idea/runConfigurations/Run_demo__debug_.xml | 1 + Cargo.toml | 4 +-- maplibre/src/debug/mod.rs | 3 +- maplibre/src/platform/mod.rs | 3 ++ maplibre/src/render/main_pass.rs | 2 ++ maplibre/src/render/shaders/sdf.vertex.wgsl | 2 +- maplibre/src/render/systems/cleanup_system.rs | 6 ++-- maplibre/src/render/translucent_pass.rs | 2 ++ maplibre/src/sdf/mod.rs | 7 +--- maplibre/src/sdf/populate_world_system.rs | 4 +-- maplibre/src/sdf/queue_system.rs | 7 ---- maplibre/src/sdf/tessellation.rs | 35 ++++++++++++++++--- maplibre/src/sdf/text.rs | 6 ++-- maplibre/src/sdf/upload_system.rs | 22 ++++-------- maplibre/src/vector/process_vector.rs | 16 ++++----- maplibre/src/vector/resource_system.rs | 1 - maplibre/src/vector/transferables.rs | 8 ++--- maplibre/src/vector/upload_system.rs | 5 +++ 18 files changed, 77 insertions(+), 57 deletions(-) diff --git a/.idea/runConfigurations/Run_demo__debug_.xml b/.idea/runConfigurations/Run_demo__debug_.xml index 008d47370..ef52bbb6a 100644 --- a/.idea/runConfigurations/Run_demo__debug_.xml +++ b/.idea/runConfigurations/Run_demo__debug_.xml @@ -4,6 +4,7 @@