mirror of
https://github.com/penpot/penpot.git
synced 2026-08-07 21:38:48 +00:00
⚡ Keep HiDPI tile count stable with DPR-scaled textures
World tile size stays BASE/zoom so viewport tile count matches DPR=1, while device coverage uses BASE*dpr for backbuffer placement.
This commit is contained in:
parent
f352127c8a
commit
34d95895c5
@ -99,7 +99,7 @@ pub fn render_debug_viewbox_tiles(render_state: &mut RenderState) {
|
||||
paint.set_color(skia::Color::MAGENTA);
|
||||
paint.set_stroke_width(1.);
|
||||
|
||||
let tile_size = tiles::get_tile_size(scale);
|
||||
let tile_size = tiles::get_tile_size(scale, render_state.viewbox.dpr);
|
||||
let tile_rect = tiles::get_tiles_for_rect(render_state.viewbox.area, tile_size);
|
||||
let tiles::TileRect(sx, sy, ex, ey) = tile_rect;
|
||||
|
||||
@ -108,7 +108,7 @@ pub fn render_debug_viewbox_tiles(render_state: &mut RenderState) {
|
||||
let debug_font = get_resources().fonts.debug_font();
|
||||
canvas.draw_str(str_rect, skia::Point::new(100.0, 100.0), debug_font, &paint);
|
||||
|
||||
let tile_size = tiles::get_tile_size(scale);
|
||||
let tile_size = tiles::get_tile_size(scale, render_state.viewbox.dpr);
|
||||
for tile in tile_rect.iter(true) {
|
||||
let tiles::Tile(x, y) = tile;
|
||||
let rect = tile.get_rect_with_size(tile_size);
|
||||
|
||||
@ -32,12 +32,12 @@ impl Tile {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_rect_with_offset(&self, offset: &skia::Point) -> skia::Rect {
|
||||
pub fn get_rect_with_offset(&self, offset: &skia::Point, tile_size_px: f32) -> skia::Rect {
|
||||
skia::Rect::from_xywh(
|
||||
self.0 as f32 * TILE_SIZE - offset.x,
|
||||
self.1 as f32 * TILE_SIZE - offset.y,
|
||||
TILE_SIZE,
|
||||
TILE_SIZE,
|
||||
self.0 as f32 * tile_size_px - offset.x,
|
||||
self.1 as f32 * tile_size_px - offset.y,
|
||||
tile_size_px,
|
||||
tile_size_px,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -209,11 +209,40 @@ impl TileViewbox {
|
||||
}
|
||||
}
|
||||
|
||||
pub const TILE_SIZE: f32 = 512.;
|
||||
/// Base tile texture size at DPR=1 (device pixels).
|
||||
pub const TILE_SIZE_BASE: f32 = 512.;
|
||||
|
||||
/// Alias for [`TILE_SIZE_BASE`]. Prefer [`device_tile_size_px`] for layout size.
|
||||
pub const TILE_SIZE: f32 = TILE_SIZE_BASE;
|
||||
|
||||
/// Device-pixel coverage of one tile on the backbuffer at the given view DPR.
|
||||
/// At DPR=2 this is 1024 — used for placing sprites, not necessarily for
|
||||
/// rasterizing them (see content-quality / interactive LOD).
|
||||
#[inline(always)]
|
||||
pub fn device_tile_size_px(dpr: f32) -> f32 {
|
||||
(TILE_SIZE_BASE * dpr.max(0.01)).round().max(1.0)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_tile_dimensions() -> skia::ISize {
|
||||
(TILE_SIZE as i32, TILE_SIZE as i32).into()
|
||||
pub fn device_tile_size_px_i32(dpr: f32) -> i32 {
|
||||
device_tile_size_px(dpr) as i32
|
||||
}
|
||||
|
||||
/// Alias kept for call sites that mean "full-quality raster size == device coverage".
|
||||
#[inline(always)]
|
||||
pub fn tile_size_px(dpr: f32) -> f32 {
|
||||
device_tile_size_px(dpr)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn tile_size_px_i32(dpr: f32) -> i32 {
|
||||
device_tile_size_px_i32(dpr)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_tile_dimensions(dpr: f32) -> skia::ISize {
|
||||
let s = device_tile_size_px_i32(dpr);
|
||||
(s, s).into()
|
||||
}
|
||||
|
||||
pub fn get_tiles_for_rect(rect: skia::Rect, tile_size: f32) -> TileRect {
|
||||
@ -227,7 +256,7 @@ pub fn get_tiles_for_rect(rect: skia::Rect, tile_size: f32) -> TileRect {
|
||||
}
|
||||
|
||||
pub fn get_tiles_for_viewbox(viewbox: &Viewbox) -> TileRect {
|
||||
let tile_size = get_tile_size(viewbox.get_scale());
|
||||
let tile_size = get_tile_size(viewbox.get_scale(), viewbox.dpr);
|
||||
get_tiles_for_rect(viewbox.area, tile_size)
|
||||
}
|
||||
|
||||
@ -241,20 +270,22 @@ pub fn get_tile_center_for_viewbox(viewbox: &Viewbox) -> Tile {
|
||||
Tile((ex - sx) / 2, (ey - sy) / 2)
|
||||
}
|
||||
|
||||
pub fn get_tile_pos(Tile(x, y): Tile, scale: f32) -> (f32, f32) {
|
||||
(
|
||||
x as f32 * get_tile_size(scale),
|
||||
y as f32 * get_tile_size(scale),
|
||||
)
|
||||
pub fn get_tile_pos(Tile(x, y): Tile, scale: f32, dpr: f32) -> (f32, f32) {
|
||||
let ts = get_tile_size(scale, dpr);
|
||||
(x as f32 * ts, y as f32 * ts)
|
||||
}
|
||||
|
||||
pub fn get_tile_size(scale: f32) -> f32 {
|
||||
1. / scale * TILE_SIZE
|
||||
/// World/document size of one tile at `scale = zoom * dpr`.
|
||||
/// Always `BASE / zoom` so viewport tile *count* matches DPR=1 regardless of
|
||||
/// the raster tile texture size used for LOD.
|
||||
pub fn get_tile_size(scale: f32, dpr: f32) -> f32 {
|
||||
let zoom = (scale / dpr.max(0.01)).max(1e-6);
|
||||
TILE_SIZE_BASE / zoom
|
||||
}
|
||||
|
||||
pub fn get_tile_rect(tile: Tile, scale: f32) -> skia::Rect {
|
||||
let (tx, ty) = get_tile_pos(tile, scale);
|
||||
let ts = get_tile_size(scale);
|
||||
pub fn get_tile_rect(tile: Tile, scale: f32, dpr: f32) -> skia::Rect {
|
||||
let (tx, ty) = get_tile_pos(tile, scale, dpr);
|
||||
let ts = get_tile_size(scale, dpr);
|
||||
skia::Rect::from_xywh(tx, ty, ts, ts)
|
||||
}
|
||||
|
||||
@ -384,7 +415,11 @@ impl PendingTiles {
|
||||
for (_, tile) in self.tile_order.iter() {
|
||||
let tile = *tile;
|
||||
let is_visible = tile_viewbox.visible_rect.contains(&tile);
|
||||
let is_cached = surfaces.has_cached_tile_surface(tile);
|
||||
// Interest tiles that were drawn but could not get an atlas slot must
|
||||
// not be re-queued (would loop forever with DPR-scaled 16-slot atlases).
|
||||
// Once they become visible, `has` is false until they get a real slot.
|
||||
let is_cached = surfaces.has_cached_tile_surface(tile)
|
||||
|| (!is_visible && surfaces.was_rendered_without_atlas_slot(tile));
|
||||
|
||||
match (is_visible, is_cached) {
|
||||
(true, true) => self.visible_cached.push(tile),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user