for ColoredPointSymbol {
+ fn render<'a, N, P>(
+ &self,
+ feature: &PointMarker,
+ geometry: &'a Geom,
+ _min_resolution: f64,
+ ) -> Vec, Polygon>>
+ where
+ N: AsPrimitive,
+ P: CartesianPoint3d + Clone,
+ {
+ if let Geom::Point(point) = geometry {
+ vec![RenderPrimitive::new_point_ref(
+ point,
+ PointPaint::image(
+ if feature.highlighted {
+ GREEN_PIN_IMAGE.clone()
+ } else {
+ YELLOW_PIN_IMAGE.clone()
+ },
+ Vector2::new(0.5, 0.5),
+ 1.0,
+ ),
+ )]
+ } else {
+ vec![]
+ }
+ }
+}
diff --git a/galileo/src/render/render_bundle/tessellating.rs b/galileo/src/render/render_bundle/tessellating.rs
index 0d18909..35920ad 100644
--- a/galileo/src/render/render_bundle/tessellating.rs
+++ b/galileo/src/render/render_bundle/tessellating.rs
@@ -31,14 +31,28 @@ pub(crate) struct TessellatingRenderBundle {
pub poly_tessellation: VertexBuffers,
pub points: Vec,
pub screen_ref: ScreenRefTessellation,
- pub images: Vec<(usize, [ImageVertex; 4])>,
+ pub images: Vec,
pub clip_area: Option>,
- pub image_store: Vec>,
+ pub image_store: Vec,
pub primitives: Vec,
vacant_ids: Vec,
+ vacant_image_ids: Vec,
+ vacant_image_store_ids: Vec,
buffer_size: usize,
}
+#[derive(Debug, Clone)]
+pub(crate) enum ImageStoreInfo {
+ Vacant,
+ Image(Arc),
+}
+
+#[derive(Debug, Clone)]
+pub(crate) enum ImageInfo {
+ Vacant,
+ Image((usize, [ImageVertex; 4])),
+}
+
pub(crate) type ScreenRefTessellation = VertexBuffers;
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
@@ -75,6 +89,8 @@ impl TessellatingRenderBundle {
clip_area: None,
image_store: Vec::new(),
vacant_ids: vec![],
+ vacant_image_ids: vec![],
+ vacant_image_store_ids: vec![],
buffer_size: 0,
}
}
@@ -112,40 +128,38 @@ impl TessellatingRenderBundle {
paint: ImagePaint,
) -> PrimitiveId {
let opacity = paint.opacity as f32 / 255.0;
- let image_index = self.images.len();
self.buffer_size += image.bytes.len() + std::mem::size_of::() * 4;
let index = self.add_image_to_store(Arc::new(image));
- self.images.push((
- index,
- [
- ImageVertex {
- position: [vertices[0].x() as f32, vertices[0].y() as f32],
- opacity,
- tex_coords: [0.0, 1.0],
- offset: [0.0, 0.0],
- },
- ImageVertex {
- position: [vertices[1].x() as f32, vertices[1].y() as f32],
- opacity,
- tex_coords: [0.0, 0.0],
- offset: [0.0, 0.0],
- },
- ImageVertex {
- position: [vertices[3].x() as f32, vertices[3].y() as f32],
- opacity,
- tex_coords: [1.0, 1.0],
- offset: [0.0, 0.0],
- },
- ImageVertex {
- position: [vertices[2].x() as f32, vertices[2].y() as f32],
- opacity,
- tex_coords: [1.0, 0.0],
- offset: [0.0, 0.0],
- },
- ],
- ));
+ let vertices = [
+ ImageVertex {
+ position: [vertices[0].x() as f32, vertices[0].y() as f32],
+ opacity,
+ tex_coords: [0.0, 1.0],
+ offset: [0.0, 0.0],
+ },
+ ImageVertex {
+ position: [vertices[1].x() as f32, vertices[1].y() as f32],
+ opacity,
+ tex_coords: [0.0, 0.0],
+ offset: [0.0, 0.0],
+ },
+ ImageVertex {
+ position: [vertices[3].x() as f32, vertices[3].y() as f32],
+ opacity,
+ tex_coords: [1.0, 1.0],
+ offset: [0.0, 0.0],
+ },
+ ImageVertex {
+ position: [vertices[2].x() as f32, vertices[2].y() as f32],
+ opacity,
+ tex_coords: [1.0, 0.0],
+ offset: [0.0, 0.0],
+ },
+ ];
+
+ let image_index = self.add_image_info(index, vertices);
let id = self.primitives.len();
@@ -167,7 +181,6 @@ impl TessellatingRenderBundle {
P: CartesianPoint3d,
{
let opacity = opacity as f32 / 255.0;
- let image_index = self.images.len();
self.buffer_size += image.bytes.len() + size_of::() * 4;
@@ -176,39 +189,50 @@ impl TessellatingRenderBundle {
let offset_y = offset[1] * height;
let index = self.add_image_to_store(image);
- self.images.push((
- index,
- [
- ImageVertex {
- position,
- opacity,
- tex_coords: [0.0, 1.0],
- offset: [offset_x, offset_y - height],
- },
- ImageVertex {
- position,
- opacity,
- tex_coords: [0.0, 0.0],
- offset: [offset_x, offset_y],
- },
- ImageVertex {
- position,
- opacity,
- tex_coords: [1.0, 1.0],
- offset: [offset_x + width, offset_y - height],
- },
- ImageVertex {
- position,
- opacity,
- tex_coords: [1.0, 0.0],
- offset: [offset_x + width, offset_y],
- },
- ],
- ));
+ let vertices = [
+ ImageVertex {
+ position,
+ opacity,
+ tex_coords: [0.0, 1.0],
+ offset: [offset_x, offset_y - height],
+ },
+ ImageVertex {
+ position,
+ opacity,
+ tex_coords: [0.0, 0.0],
+ offset: [offset_x, offset_y],
+ },
+ ImageVertex {
+ position,
+ opacity,
+ tex_coords: [1.0, 1.0],
+ offset: [offset_x + width, offset_y - height],
+ },
+ ImageVertex {
+ position,
+ opacity,
+ tex_coords: [1.0, 0.0],
+ offset: [offset_x + width, offset_y],
+ },
+ ];
+
+ let image_index = self.add_image_info(index, vertices);
PrimitiveInfo::Image { image_index }
}
+ fn add_image_info(&mut self, image_store_index: usize, vertices: [ImageVertex; 4]) -> usize {
+ if let Some(id) = self.vacant_image_ids.pop() {
+ self.images[id] = ImageInfo::Image((image_store_index, vertices));
+ id
+ } else {
+ let index = self.images.len();
+ self.images
+ .push(ImageInfo::Image((image_store_index, vertices)));
+ index
+ }
+ }
+
fn add_primitive_info(&mut self, info: PrimitiveInfo) -> PrimitiveId {
if let Some(id) = self.vacant_ids.pop() {
self.primitives[id] = info;
@@ -222,14 +246,24 @@ impl TessellatingRenderBundle {
fn add_image_to_store(&mut self, image: Arc) -> usize {
for (i, stored) in self.image_store.iter().enumerate() {
- if Arc::ptr_eq(stored, &image) {
- return i;
+ match stored {
+ ImageStoreInfo::Vacant => {}
+ ImageStoreInfo::Image(stored) => {
+ if Arc::ptr_eq(stored, &image) {
+ return i;
+ }
+ }
}
}
- let index = self.image_store.len();
- self.image_store.push(image);
- index
+ if let Some(id) = self.vacant_image_store_ids.pop() {
+ self.image_store[id] = ImageStoreInfo::Image(image);
+ id
+ } else {
+ let index = self.image_store.len();
+ self.image_store.push(ImageStoreInfo::Image(image));
+ index
+ }
}
pub fn add(
@@ -306,10 +340,36 @@ impl TessellatingRenderBundle {
if index >= self.images.len() {
Err(GalileoError::Generic("index out of bounds".into()))
} else {
- let (image_id, _) = self.images.remove(index);
- let image = self.image_store.remove(image_id);
+ let image_id = match std::mem::replace(&mut self.images[index], ImageInfo::Vacant) {
+ ImageInfo::Vacant => {
+ // this should not happen
+ return Err(GalileoError::Generic(
+ "tried to replace vacant image with vacant slot".into(),
+ ));
+ }
+ ImageInfo::Image((image_id, _)) => {
+ self.vacant_image_ids.push(index);
+ image_id
+ }
+ };
+
+ let stored_image_unused = self.images.iter().all(|info| match info {
+ ImageInfo::Vacant => false,
+ ImageInfo::Image((i, _)) => *i != image_id,
+ });
- self.buffer_size -= image.bytes.len() + size_of::() * 4;
+ if stored_image_unused {
+ match std::mem::replace(&mut self.image_store[image_id], ImageStoreInfo::Vacant) {
+ ImageStoreInfo::Vacant => {
+ // this should not happen
+ }
+ ImageStoreInfo::Image(image) => {
+ self.vacant_image_store_ids.push(image_id);
+
+ self.buffer_size -= image.bytes.len() + size_of::() * 4;
+ }
+ }
+ }
for info in &mut self.primitives {
match info {
@@ -607,12 +667,19 @@ impl TessellatingRenderBundle {
.ok_or(GalileoError::Generic("primitive does not exist".into()))?;
match info {
PrimitiveInfo::Image { image_index } => {
- let (_, vertices) = self
+ match self
.images
.get_mut(*image_index)
- .ok_or(GalileoError::Generic("invalid image id".into()))?;
- for vertex in vertices {
- vertex.opacity = paint.opacity as f32 / 255.0;
+ .ok_or(GalileoError::Generic("invalid image id".into()))?
+ {
+ ImageInfo::Vacant => {
+ return Err(GalileoError::Generic("tried to modify vacant image".into()))
+ }
+ ImageInfo::Image((_, vertices)) => {
+ for vertex in vertices {
+ vertex.opacity = paint.opacity as f32 / 255.0;
+ }
+ }
}
}
_ => return Err(GalileoError::Generic("invalid primitive type".into())),
@@ -913,19 +980,27 @@ impl TessellatingRenderBundle {
let Some(transform) = view.map_to_scene_transform() else {
return;
};
- self.images.sort_by(|(_, vertex_set_a), (_, vertex_set_b)| {
- let point_a = Point3d::new(
- vertex_set_a[0].position[0] as f64,
- vertex_set_a[0].position[1] as f64,
- 0.0,
- )
- .to_homogeneous();
- let point_b = Point3d::new(
- vertex_set_b[0].position[0] as f64,
- vertex_set_b[0].position[1] as f64,
- 0.0,
- )
- .to_homogeneous();
+ self.images.sort_by(|info_a, info_b| {
+ let point_a = match info_a {
+ ImageInfo::Vacant => Point3d::new(0.0, 0.0, 0.0).to_homogeneous(),
+ ImageInfo::Image((_, vertex_set_a)) => Point3d::new(
+ vertex_set_a[0].position[0] as f64,
+ vertex_set_a[0].position[1] as f64,
+ 0.0,
+ )
+ .to_homogeneous(),
+ };
+
+ let point_b = match info_b {
+ ImageInfo::Vacant => Point3d::new(0.0, 0.0, 0.0).to_homogeneous(),
+ ImageInfo::Image((_, vertex_set_b)) => Point3d::new(
+ vertex_set_b[0].position[0] as f64,
+ vertex_set_b[0].position[1] as f64,
+ 0.0,
+ )
+ .to_homogeneous(),
+ };
+
let projected_a = transform * point_a;
let projected_b = transform * point_b;
diff --git a/galileo/src/render/render_bundle/tessellating/serialization.rs b/galileo/src/render/render_bundle/tessellating/serialization.rs
index 0aed90c..f2619b9 100644
--- a/galileo/src/render/render_bundle/tessellating/serialization.rs
+++ b/galileo/src/render/render_bundle/tessellating/serialization.rs
@@ -1,6 +1,6 @@
use crate::decoded_image::DecodedImage;
use crate::render::render_bundle::tessellating::{
- PolyVertex, PrimitiveInfo, ScreenRefVertex, TessellatingRenderBundle,
+ ImageInfo, ImageStoreInfo, PolyVertex, PrimitiveInfo, ScreenRefVertex, TessellatingRenderBundle,
};
use lyon::lyon_tessellation::VertexBuffers;
use serde::{Deserialize, Serialize};
@@ -12,9 +12,11 @@ pub(crate) struct TessellatingRenderBundleBytes {
pub poly_tessellation: PolyVertexBuffersBytes,
pub points: Vec,
pub screen_ref: ScreenRefVertexBuffersBytes,
- pub images: Vec,
+ pub images: Vec>,
pub primitives: Vec,
- pub image_store: Vec<(u32, u32, Vec)>,
+ pub image_store: Vec)>>,
+ pub vacant_image_ids: Vec,
+ pub vacant_image_store_ids: Vec,
pub clip_area: Option,
pub bundle_size: usize,
}
@@ -89,17 +91,27 @@ impl TessellatingRenderBundle {
images: self
.images
.into_iter()
- .map(|(image_index, vertices)| ImageBytes {
- image_index,
- vertices: bytemuck::cast_vec(vertices.to_vec()),
+ .map(|image_info| match image_info {
+ ImageInfo::Vacant => None,
+ ImageInfo::Image((image_index, vertices)) => Some(ImageBytes {
+ image_index,
+ vertices: bytemuck::cast_vec(vertices.to_vec()),
+ }),
})
.collect(),
primitives: self.primitives,
image_store: self
.image_store
.into_iter()
- .map(|image| (image.dimensions.0, image.dimensions.1, image.bytes.clone()))
+ .map(|image_info| match image_info {
+ ImageStoreInfo::Vacant => None,
+ ImageStoreInfo::Image(image) => {
+ Some((image.dimensions.0, image.dimensions.1, image.bytes.clone()))
+ }
+ })
.collect(),
+ vacant_image_ids: self.vacant_image_ids,
+ vacant_image_store_ids: self.vacant_image_store_ids,
clip_area: self.clip_area.map(|v| v.into()),
bundle_size: self.buffer_size,
};
@@ -115,30 +127,34 @@ impl TessellatingRenderBundle {
images: bundle
.images
.into_iter()
- .map(
- |ImageBytes {
- image_index,
- vertices,
- }| {
+ .map(|item| match item {
+ Some(ImageBytes {
+ image_index,
+ vertices,
+ }) => {
let vertices = bytemuck::cast_vec(vertices)
.try_into()
.expect("invalid vector length");
- (image_index, vertices)
- },
- )
+ ImageInfo::Image((image_index, vertices))
+ }
+ None => ImageInfo::Vacant,
+ })
.collect(),
primitives: bundle.primitives,
image_store: bundle
.image_store
.into_iter()
- .map(|(width, height, bytes)| {
- Arc::new(DecodedImage {
+ .map(|stored| match stored {
+ Some((width, height, bytes)) => ImageStoreInfo::Image(Arc::new(DecodedImage {
bytes,
dimensions: (width, height),
- })
+ })),
+ None => ImageStoreInfo::Vacant,
})
.collect(),
+ vacant_image_ids: bundle.vacant_image_ids,
+ vacant_image_store_ids: bundle.vacant_image_store_ids,
clip_area: bundle.clip_area.map(|v| v.into_typed_unchecked()),
buffer_size: bundle.bundle_size,
vacant_ids: vec![],
diff --git a/galileo/src/render/wgpu/mod.rs b/galileo/src/render/wgpu/mod.rs
index 65ec86c..3efc3c2 100644
--- a/galileo/src/render/wgpu/mod.rs
+++ b/galileo/src/render/wgpu/mod.rs
@@ -26,6 +26,7 @@ use crate::render::wgpu::pipelines::Pipelines;
use crate::view::MapView;
use crate::Color;
+use super::render_bundle::tessellating::{ImageInfo, ImageStoreInfo};
use super::{Canvas, PackedBundle, RenderOptions};
mod pipelines;
@@ -862,23 +863,35 @@ impl WgpuPackedBundle {
let textures: Vec<_> = image_store
.iter()
- .map(|decoded_image| {
- render_set.pipelines.image_pipeline().create_image_texture(
- &renderer.device,
- &renderer.queue,
- decoded_image,
- )
+ .map(|stored| match stored {
+ ImageStoreInfo::Vacant => None,
+ ImageStoreInfo::Image(decoded_image) => {
+ Some(render_set.pipelines.image_pipeline().create_image_texture(
+ &renderer.device,
+ &renderer.queue,
+ decoded_image,
+ ))
+ }
})
.collect();
let mut image_buffers = vec![];
- for (image_index, vertices) in images {
- let image = render_set.pipelines.image_pipeline().create_image(
- &renderer.device,
- textures[*image_index].clone(),
- vertices,
- );
- image_buffers.push(image);
+ for image_info in images {
+ if let ImageInfo::Image((image_index, vertices)) = image_info {
+ let image = render_set.pipelines.image_pipeline().create_image(
+ &renderer.device,
+ textures
+ .get(*image_index)
+ .expect("texture at index must exist")
+ .clone()
+ .expect("image texture must not be None")
+ .clone(),
+ vertices,
+ );
+ image_buffers.push(image);
+ } else {
+ // ignore vacant image slots
+ }
}
Self {