Skip to content

Commit ac1fd97

Browse files
fourlexboehmAlex BoehmMaximkaaa
authored
feat: support HiDPI scaling in galileo-egui (galileo-map#220)
--------- Co-authored-by: Alex Boehm <[email protected]> Co-authored-by: Maxim <[email protected]>
1 parent e11ac55 commit ac1fd97

File tree

13 files changed

+126
-56
lines changed

13 files changed

+126
-56
lines changed

galileo-egui/src/egui_map.rs

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ pub struct EguiMapState {
146146
texture_view: TextureView,
147147
event_processor: EventProcessor,
148148
messenger: MapStateMessenger,
149+
map_ready: bool,
149150
}
150151

151152
impl<'a> EguiMapState {
@@ -177,6 +178,7 @@ impl<'a> EguiMapState {
177178
// This size will be replaced by the UI on the first frame.
178179
let size = Size::new(1, 1);
179180
map.set_size(size.cast());
181+
map.set_view(map.view().with_dpi_scale_factor(ctx.pixels_per_point()));
180182

181183
let mut renderer = WgpuRenderer::new_with_device_and_texture(
182184
render_state.device.clone(),
@@ -209,6 +211,7 @@ impl<'a> EguiMapState {
209211
texture_view: texture,
210212
event_processor,
211213
messenger,
214+
map_ready: false,
212215
}
213216
}
214217

@@ -219,10 +222,13 @@ impl<'a> EguiMapState {
219222

220223
/// Renders the map into UI.
221224
pub fn render(&mut self, ui: &mut egui::Ui) {
222-
let available_size = ui.available_size().floor();
223-
let map_size = self.renderer.size().cast::<f32>();
225+
let logical_size = ui.available_size().floor();
226+
let pixels_per_point = ui.ctx().pixels_per_point();
227+
let physical_size = logical_size * pixels_per_point;
224228

225-
let (rect, response) = ui.allocate_exact_size(available_size, Sense::click_and_drag());
229+
let (rect, response) = ui.allocate_exact_size(logical_size, Sense::click_and_drag());
230+
231+
let renderer_size = self.renderer.size().cast::<f32>();
226232

227233
let attributions = self.collect_attributions();
228234
if attributions.is_some() {
@@ -243,8 +249,15 @@ impl<'a> EguiMapState {
243249

244250
self.map.animate();
245251

246-
if available_size[0] != map_size.width() || available_size[1] != map_size.height() {
247-
self.resize_map(available_size);
252+
if physical_size[0] != renderer_size.width() || physical_size[1] != renderer_size.height() {
253+
self.map_ready = true;
254+
self.resize_map(logical_size, pixels_per_point);
255+
self.map
256+
.set_view(self.map.view().with_dpi_scale_factor(pixels_per_point));
257+
}
258+
259+
if self.map_ready {
260+
self.map.load_layers();
248261
}
249262

250263
if self.requires_redraw.swap(false, Ordering::Relaxed) {
@@ -253,7 +266,7 @@ impl<'a> EguiMapState {
253266

254267
Image::new(ImageSource::Texture(SizedTexture::new(
255268
self.texture_id,
256-
Vec2::new(map_size.width(), map_size.height()),
269+
Vec2::new(renderer_size.width(), renderer_size.height()),
257270
)))
258271
.paint_at(ui, rect);
259272
}
@@ -309,30 +322,42 @@ impl<'a> EguiMapState {
309322
self.messenger.clone()
310323
}
311324

312-
fn resize_map(&mut self, size: Vec2) {
313-
log::trace!("Resizing map to size: {size:?}");
325+
fn resize_map(&mut self, logical_size: Vec2, pixels_per_point: f32) {
326+
log::trace!(
327+
"Resizing map to logical size: {logical_size:?}, pixels_per_point: {pixels_per_point}"
328+
);
314329

315-
let size = Size::new(size.x as f64, size.y as f64);
316-
self.map.set_size(size);
330+
// Set the logical size for the map
331+
let logical_size_f64 = Size::new(logical_size.x as f64, logical_size.y as f64);
332+
self.map.set_size(logical_size_f64);
317333

318-
let size = Size::new(size.width() as u32, size.height() as u32);
319-
self.renderer.resize(size);
334+
// Resize the renderer to physical size (accounting for pixel density)
335+
let physical_size = Size::new(
336+
(logical_size.x * pixels_per_point) as u32,
337+
(logical_size.y * pixels_per_point) as u32,
338+
);
339+
self.renderer.resize(physical_size);
320340

321341
// After renderer is resized, a new texture is created, so we need to update its id that we
322342
// use in UI.
323343
let texture = self
324344
.renderer
325345
.get_target_texture_view()
326346
.expect("failed to get map texture");
347+
348+
// Use Linear filtering for better quality on HiDPI displays
349+
let filter_mode = if pixels_per_point > 1.0 {
350+
FilterMode::Linear
351+
} else {
352+
FilterMode::Nearest
353+
};
354+
log::info!("Using filter mode: {filter_mode:?}");
355+
327356
let texture_id = self
328357
.egui_render_state
329358
.renderer
330359
.write()
331-
.register_native_texture(
332-
&self.egui_render_state.device,
333-
&texture,
334-
FilterMode::Nearest,
335-
);
360+
.register_native_texture(&self.egui_render_state.device, &texture, filter_mode);
336361

337362
self.texture_id = texture_id;
338363
self.texture_view = texture;

galileo-mvt/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ impl MvtFeature {
285285
values: &[MvtValue],
286286
) -> Result<HashMap<String, MvtValue>, GalileoMvtError> {
287287
let mut properties = HashMap::new();
288-
if tags.len() % 2 != 0 {
288+
if !tags.len().is_multiple_of(2) {
289289
return Err(GalileoMvtError::Generic(
290290
"Invalid number of tags in feature".into(),
291291
));

galileo/src/layer/feature_layer/bundle_store.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,13 @@ impl BundleStore {
9191
self.required_update.update_all();
9292
}
9393

94-
pub(super) fn with_bundle(&mut self, predicate: impl FnOnce(&mut RenderBundle) -> FeatureId) {
94+
pub(super) fn with_bundle(
95+
&mut self,
96+
predicate: impl FnOnce(&mut RenderBundle) -> FeatureId,
97+
dpi_scale_factor: f32,
98+
) {
9599
let (bundle_id, curr_bundle) = {
96-
let v = self.curr_bundle();
100+
let v = self.curr_bundle(dpi_scale_factor);
97101
(v.0, &mut v.1)
98102
};
99103

@@ -104,10 +108,11 @@ impl BundleStore {
104108
self.feature_to_bundle_map.insert(feature_id, bundle_id);
105109
}
106110

107-
fn curr_bundle(&mut self) -> &mut (BundleId, RenderBundle) {
111+
fn curr_bundle(&mut self, dpi_scale_factor: f32) -> &mut (BundleId, RenderBundle) {
108112
if self.last_bundle_is_full() {
109113
let new_id = BundleId::next();
110-
self.unpacked.push((new_id, RenderBundle::default()));
114+
self.unpacked
115+
.push((new_id, RenderBundle::new(dpi_scale_factor)));
111116
}
112117

113118
let idx = self.unpacked.len() - 1;

galileo/src/layer/feature_layer/mod.rs

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -232,33 +232,40 @@ where
232232
) {
233233
let lod = self.select_lod(view.resolution());
234234
let mut store = lod.bundles.lock();
235+
let dpi_scale_factor = view.dpi_scale_factor();
235236

236237
match store.required_update() {
237238
UpdateType::All => {
238239
for (id, feature) in self.features.iter() {
239-
store.with_bundle(|bundle| {
240-
if let Some(projected) = feature.geometry().project(&*projection) {
241-
self.symbol
242-
.render(feature, &projected, lod.min_resolution, bundle);
243-
}
244-
245-
id
246-
});
240+
store.with_bundle(
241+
|bundle| {
242+
if let Some(projected) = feature.geometry().project(&*projection) {
243+
self.symbol
244+
.render(feature, &projected, lod.min_resolution, bundle);
245+
}
246+
247+
id
248+
},
249+
dpi_scale_factor,
250+
);
247251
}
248252
}
249253
UpdateType::Selected(ids) => {
250254
for id in ids {
251255
let Some(feature) = self.features.get(id) else {
252256
continue;
253257
};
254-
store.with_bundle(|bundle| {
255-
if let Some(projected) = feature.geometry().project(&*projection) {
256-
self.symbol
257-
.render(feature, &projected, lod.min_resolution, bundle);
258-
}
259-
260-
id
261-
});
258+
store.with_bundle(
259+
|bundle| {
260+
if let Some(projected) = feature.geometry().project(&*projection) {
261+
self.symbol
262+
.render(feature, &projected, lod.min_resolution, bundle);
263+
}
264+
265+
id
266+
},
267+
dpi_scale_factor,
268+
);
262269
}
263270
}
264271
UpdateType::None => {}

galileo/src/layer/vector_tile_layer/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ impl VectorTileLayer {
239239
view: &MapView,
240240
canvas: &mut dyn Canvas,
241241
) -> Option<Box<dyn PackedBundle>> {
242-
let mut bundle = RenderBundle::default();
242+
let mut bundle = RenderBundle::new(view.dpi_scale_factor());
243243
let bbox = view.get_bbox()?;
244244
let bounds = Polygon::new(
245245
ClosedContour::new(vec![

galileo/src/map/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ impl Map {
7575
layer.prepare(&self.view);
7676
}
7777
}
78-
7978
/// Request redraw of the map.
8079
pub fn redraw(&self) {
8180
if let Some(messenger) = &self.messenger {

galileo/src/render/render_bundle/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ pub struct RenderBundle {
2727
}
2828

2929
impl RenderBundle {
30+
/// Creates a new render bundle with the given DPI scale factor.
31+
pub fn new(dpi_scale_factor: f32) -> Self {
32+
Self {
33+
world_set: WorldRenderSet::new(dpi_scale_factor),
34+
screen_sets: Vec::new(),
35+
}
36+
}
37+
3038
/// Adds an image to the bundle.
3139
pub fn add_image(
3240
&mut self,

galileo/src/render/render_bundle/screen_set.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ impl ScreenRenderSet {
6767
N: AsPrimitive<f32>,
6868
P: CartesianPoint3d<Num = N>,
6969
{
70-
match TextService::shape(text, style, offset) {
70+
match TextService::shape(text, style, offset, 1.0) {
7171
Ok(TextShaping::Tessellation { glyphs, .. }) => {
7272
let mut vertices = vec![];
7373
let mut indices = vec![];

galileo/src/render/render_bundle/world_set.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub(crate) struct WorldRenderSet {
3131
pub clip_area: Option<VertexBuffers<PolyVertex, u32>>,
3232
pub image_store: Vec<Arc<DecodedImage>>,
3333
pub buffer_size: usize,
34+
dpi_scale_factor: f32,
3435
}
3536

3637
#[repr(C)]
@@ -50,19 +51,20 @@ pub(crate) struct ScreenRefVertex {
5051

5152
impl Default for WorldRenderSet {
5253
fn default() -> Self {
53-
Self::new()
54+
Self::new(1.0)
5455
}
5556
}
5657

5758
impl WorldRenderSet {
58-
pub fn new() -> Self {
59+
pub fn new(dpi_scale_factor: f32) -> Self {
5960
Self {
6061
poly_tessellation: VertexBuffers::new(),
6162
points: Vec::new(),
6263
images: Vec::new(),
6364
clip_area: None,
6465
image_store: Vec::new(),
6566
buffer_size: 0,
67+
dpi_scale_factor,
6668
}
6769
}
6870

@@ -246,7 +248,7 @@ impl WorldRenderSet {
246248
let path = path_builder.build();
247249

248250
let vertex_constructor = LineVertexConstructor {
249-
width: paint.width as f32,
251+
width: paint.width as f32 * self.dpi_scale_factor,
250252
offset: paint.offset as f32,
251253
color: paint.color.to_f32_array(),
252254
resolution: min_resolution as f32,
@@ -374,7 +376,7 @@ impl WorldRenderSet {
374376
P: CartesianPoint3d<Num = N>,
375377
{
376378
let mut path_builder = BuilderWithAttributes::new(0);
377-
build_contour_path(&mut path_builder, shape, scale);
379+
build_contour_path(&mut path_builder, shape, scale * self.dpi_scale_factor);
378380
let path = path_builder.build();
379381

380382
let start_vertex_count = self.poly_tessellation.vertices.len();
@@ -475,7 +477,7 @@ impl WorldRenderSet {
475477

476478
let is_full_circle = (dr - std::f32::consts::PI * 2.0).abs() < TOLERANCE;
477479

478-
let mut contour = get_circle_sector(radius, start_angle, end_angle);
480+
let mut contour = get_circle_sector(radius * self.dpi_scale_factor, start_angle, end_angle);
479481
let first_index = self.poly_tessellation.vertices.len() as u32;
480482

481483
let start_vertex_count = self.poly_tessellation.vertices.len();
@@ -507,15 +509,16 @@ impl WorldRenderSet {
507509
self.poly_tessellation.vertices.append(&mut vertices);
508510
self.poly_tessellation.indices.append(&mut indices);
509511

510-
if outline.is_some() {
512+
if let Some(mut outline) = outline {
511513
if !is_full_circle {
512514
contour.push(Point2::new(0.0, 0.0));
513515
}
516+
outline.width *= self.dpi_scale_factor as f64;
514517
self.add_shape(
515518
position,
516519
Color::TRANSPARENT,
517520
radius,
518-
outline,
521+
Some(outline),
519522
&ClosedContour::new(contour),
520523
offset,
521524
);
@@ -554,7 +557,7 @@ impl WorldRenderSet {
554557
N: AsPrimitive<f32>,
555558
P: CartesianPoint3d<Num = N>,
556559
{
557-
match TextService::shape(text, style, offset) {
560+
match TextService::shape(text, style, offset, self.dpi_scale_factor) {
558561
Ok(TextShaping::Tessellation { glyphs, .. }) => {
559562
for glyph in glyphs {
560563
let vertices_start = self.poly_tessellation.vertices.len() as u32;

galileo/src/render/text/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ pub trait TextRasterizer {
119119
style: &TextStyle,
120120
offset: Vector2<f32>,
121121
font_provider: &dyn FontProvider,
122+
dpi_scale_factor: f32,
122123
) -> Result<TextShaping, FontServiceError>;
123124
}
124125

0 commit comments

Comments
 (0)