mirror of
https://github.com/penpot/penpot.git
synced 2026-08-10 14:59:08 +00:00
⚡ Keep tile grid zoom-only; scale GPU tiles by DPR
Document tile indices no longer densify with devicePixelRatio, so shapes stay on the same tiles across HiDPI while physical atlas slots grow as 512×dpr for sharp rasterization. AI-assisted-by: Cursor Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
ccdab34ed5
commit
b48021b8ab
@ -151,13 +151,15 @@ Tile caching (via `TileTextureCache`) boosts performance:
|
||||
|
||||
## Scaling and Tile Size Utilities
|
||||
|
||||
Tile size in screen space is constant, but its actual visual size depends on zoom level (`scale`).
|
||||
The **document** tile grid depends only on zoom (`get_tile_size(zoom) = 512 / zoom`),
|
||||
so a shape occupies the same tiles at any DPR. Paint scale remains `zoom × dpr` for
|
||||
HiDPI sharpness, and each GPU tile slot is `512 × dpr` device pixels.
|
||||
|
||||
To deal with this, the system uses utility functions:
|
||||
Utility functions:
|
||||
|
||||
- `get_tile_size(scale)`: Gets the logical size of a tile at the current scale.
|
||||
- `get_tile_rect(tile, scale)`: Gets the rectangular area a tile covers.
|
||||
- `get_tile_pos(tile, scale)`: Gets the tile’s position in canvas coordinates.
|
||||
- `get_tile_size(zoom)`: Document-space size of one tile (DPR-independent).
|
||||
- `get_tile_rect(tile, zoom)` / `get_tile_pos(tile, zoom)`: Doc-space geometry.
|
||||
- `physical_tile_size(dpr)`: GPU edge length `ceil(512 × dpr)`.
|
||||
|
||||
These allow rendering logic to adapt tile positions/sizes dynamically.
|
||||
|
||||
|
||||
@ -555,7 +555,7 @@ impl RenderState {
|
||||
let surfaces = Surfaces::try_new(
|
||||
(width, height),
|
||||
sampling_options,
|
||||
tiles::get_tile_dimensions(),
|
||||
tiles::get_tile_dimensions(1.0),
|
||||
)?;
|
||||
|
||||
Self::assemble(width, height, surfaces)
|
||||
@ -876,15 +876,24 @@ impl RenderState {
|
||||
// Only when this function returns true (it means the value
|
||||
// was properly changed) the rest of the functions is called.
|
||||
if self.options.set_dpr(dpr) {
|
||||
// Grid is zoom-only; interest is a tile-count margin (not ×dpr).
|
||||
self.tile_viewbox
|
||||
.set_interest(self.options.dpr_viewport_interest_area_threshold);
|
||||
self.viewbox.set_dpr(dpr);
|
||||
get_resources().fonts.set_scale_debug_font(dpr);
|
||||
|
||||
// Viewport surfaces (Target/Backbuffer) scale with CSS×dpr.
|
||||
self.resize(
|
||||
self.viewbox.width().floor() as i32,
|
||||
self.viewbox.height().floor() as i32,
|
||||
)?;
|
||||
get_resources().fonts.set_scale_debug_font(dpr);
|
||||
self.viewbox.set_dpr(dpr);
|
||||
self.surfaces.set_dpr(dpr);
|
||||
|
||||
// Grow/shrink physical tile work surfaces + atlas slots; drop
|
||||
// cached textures (same doc grid, wrong pixel density).
|
||||
if self.surfaces.set_dpr(dpr)? {
|
||||
self.surfaces.invalidate_tile_cache();
|
||||
self.tile_viewbox.update(&self.viewbox);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -2001,8 +2010,10 @@ impl RenderState {
|
||||
|
||||
pub fn update_render_context(&mut self, tile: tiles::Tile) {
|
||||
self.current_tile = Some(tile);
|
||||
let zoom = self.viewbox.zoom();
|
||||
let scale = self.get_scale();
|
||||
self.render_area = tiles::get_tile_rect(tile, scale);
|
||||
// Doc-space tile rect depends only on zoom (DPR-independent grid).
|
||||
self.render_area = tiles::get_tile_rect(tile, zoom);
|
||||
let margins = self.surfaces.margins();
|
||||
let margin_w = margins.width as f32 / scale;
|
||||
let margin_h = margins.height as f32 / scale;
|
||||
@ -2012,6 +2023,7 @@ impl RenderState {
|
||||
self.render_area.right + margin_w,
|
||||
self.render_area.bottom + margin_h,
|
||||
);
|
||||
// Canvas CTM still uses paint scale (zoom×dpr) for HiDPI sharpness.
|
||||
self.surfaces.update_render_context(self.render_area, scale);
|
||||
}
|
||||
|
||||
@ -2942,7 +2954,8 @@ impl RenderState {
|
||||
.current_tile
|
||||
.ok_or(Error::CriticalError("Current tile not found".to_string()))?;
|
||||
let offset = self.viewbox.get_offset();
|
||||
Ok(tile.get_rect_with_offset(&offset))
|
||||
let phys = self.surfaces.physical_tile_size() as f32;
|
||||
Ok(tile.get_rect_with_offset(&offset, phys))
|
||||
}
|
||||
|
||||
pub fn get_rect_bounds(&mut self, rect: skia::Rect) -> Rect {
|
||||
@ -2970,15 +2983,14 @@ impl RenderState {
|
||||
|
||||
pub fn get_aligned_tile_bounds(&mut self, tile: tiles::Tile) -> Rect {
|
||||
let scale = self.get_scale();
|
||||
let start_tile_x =
|
||||
(self.viewbox.area.left * scale / tiles::TILE_SIZE).floor() * tiles::TILE_SIZE;
|
||||
let start_tile_y =
|
||||
(self.viewbox.area.top * scale / tiles::TILE_SIZE).floor() * tiles::TILE_SIZE;
|
||||
let phys = self.surfaces.physical_tile_size() as f32;
|
||||
let start_tile_x = (self.viewbox.area.left * scale / phys).floor() * phys;
|
||||
let start_tile_y = (self.viewbox.area.top * scale / phys).floor() * phys;
|
||||
Rect::from_xywh(
|
||||
(tile.x() as f32 * tiles::TILE_SIZE) - start_tile_x,
|
||||
(tile.y() as f32 * tiles::TILE_SIZE) - start_tile_y,
|
||||
tiles::TILE_SIZE,
|
||||
tiles::TILE_SIZE,
|
||||
(tile.x() as f32 * phys) - start_tile_x,
|
||||
(tile.y() as f32 * phys) - start_tile_y,
|
||||
phys,
|
||||
phys,
|
||||
)
|
||||
}
|
||||
|
||||
@ -2987,9 +2999,7 @@ impl RenderState {
|
||||
//
|
||||
// Unlike `get_current_tile_bounds`, which calculates bounds using the exact
|
||||
// scaled offset of the viewbox, this method snaps the origin to the nearest
|
||||
// lower multiple of `TILE_SIZE`. This ensures the tile bounds are aligned
|
||||
// with the global tile grid, which is useful for rendering tiles in a
|
||||
/// consistent and predictable layout.
|
||||
// lower multiple of the physical tile size (`512 × dpr`).
|
||||
pub fn get_current_aligned_tile_bounds(&mut self) -> Result<Rect> {
|
||||
Ok(self.get_aligned_tile_bounds(
|
||||
self.current_tile
|
||||
@ -3976,9 +3986,10 @@ impl RenderState {
|
||||
* render_shape_tree_partial_uncached, ensuring all shapes render correctly.
|
||||
*/
|
||||
pub fn get_tiles_for_shape(&mut self, shape: &Shape, tree: ShapesPoolRef) -> TileRect {
|
||||
let scale = self.get_scale();
|
||||
let extrect = self.get_cached_extrect(shape, tree, scale);
|
||||
let tile_size = tiles::get_tile_size(scale);
|
||||
let zoom = self.viewbox.zoom();
|
||||
// Extents and tile size use zoom only so the grid is DPR-independent.
|
||||
let extrect = self.get_cached_extrect(shape, tree, zoom);
|
||||
let tile_size = tiles::get_tile_size(zoom);
|
||||
let shape_tiles = tiles::get_tiles_for_rect(extrect, tile_size);
|
||||
let interest_rect = &self.tile_viewbox.interest_rect;
|
||||
// Calculate the intersection of shape_tiles with interest_rect
|
||||
|
||||
@ -92,14 +92,14 @@ pub fn render_debug_tiles_for_viewbox(render_state: &mut RenderState) {
|
||||
|
||||
// Renders the tiles in the viewbox
|
||||
pub fn render_debug_viewbox_tiles(render_state: &mut RenderState) {
|
||||
let scale = render_state.get_scale();
|
||||
let zoom = render_state.viewbox.zoom();
|
||||
let canvas = render_state.surfaces.canvas(SurfaceId::Debug);
|
||||
let mut paint = skia::Paint::default();
|
||||
paint.set_style(skia::PaintStyle::Stroke);
|
||||
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(zoom);
|
||||
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(zoom);
|
||||
for tile in tile_rect.iter(true) {
|
||||
let tiles::Tile(x, y) = tile;
|
||||
let rect = tile.get_rect_with_size(tile_size);
|
||||
|
||||
@ -8,7 +8,6 @@ const SHOW_WASM_INFO: u32 = 0x08;
|
||||
// This is the extra area used for tile rendering (tiles beyond viewport).
|
||||
// Higher values pre-render more tiles, reducing empty squares during pan but using more memory.
|
||||
const VIEWPORT_INTEREST_AREA_THRESHOLD: i32 = 1;
|
||||
const MIN_DPR_VIEWPORT_INTEREST_AREA_THRESHOLD: i32 = 2;
|
||||
const MAX_BLOCKING_TIME_MS: i32 = 32;
|
||||
const NODE_BATCH_THRESHOLD: i32 = 3;
|
||||
/// Soft-drain GPU every N walker nodes on progressive Partials. Keeps ops
|
||||
@ -82,14 +81,10 @@ impl RenderOptions {
|
||||
self.capture_frames = capture_frames;
|
||||
}
|
||||
|
||||
/// Updates the dpr viewport interest area threshold.
|
||||
/// This function is updated when the dpr or the
|
||||
/// viewport_interest_area_threshold is changed
|
||||
/// Interest is measured in tile counts. The document grid no longer densifies
|
||||
/// with DPR, so this tracks the configured threshold directly (no ×dpr).
|
||||
fn update_dpr_viewport_interest_area_threshold(&mut self) {
|
||||
// TODO: this will likely need to change once we have the tile atlas in place
|
||||
self.dpr_viewport_interest_area_threshold =
|
||||
((self.dpr * self.viewport_interest_area_threshold as f32).ceil() as i32)
|
||||
.min(MIN_DPR_VIEWPORT_INTEREST_AREA_THRESHOLD);
|
||||
self.dpr_viewport_interest_area_threshold = self.viewport_interest_area_threshold;
|
||||
}
|
||||
|
||||
/// Sets the devicePixelRatio.
|
||||
|
||||
@ -14,19 +14,27 @@ use std::collections::{HashMap, HashSet};
|
||||
const TEXTURES_CACHE_CAPACITY: usize = 1024;
|
||||
const TEXTURES_BATCH_DELETE: usize = 256;
|
||||
|
||||
// This is the amount of extra space we're going to give to all the surfaces to render shapes.
|
||||
// If it's too big it could affect performance.
|
||||
const TILE_SIZE: i32 = tiles::TILE_SIZE as i32;
|
||||
/// Current / effect surfaces are `physical_tile × TILE_SIZE_MULTIPLIER` so
|
||||
/// blur/outset can bleed into a ¼-tile margin on each side.
|
||||
const TILE_SIZE_MULTIPLIER: i32 = 2;
|
||||
const TILE_MARGIN_SIZE: i32 = TILE_SIZE * TILE_SIZE_MULTIPLIER / 4;
|
||||
const TILE_DRAWABLE_RECT: IRect = IRect {
|
||||
left: TILE_MARGIN_SIZE,
|
||||
top: TILE_MARGIN_SIZE,
|
||||
right: TILE_MARGIN_SIZE + TILE_SIZE,
|
||||
bottom: TILE_MARGIN_SIZE + TILE_SIZE,
|
||||
};
|
||||
const DOC_ATLAS_MAX_DIM: i32 = 4096;
|
||||
|
||||
#[inline(always)]
|
||||
fn tile_margin_size(physical_tile: i32) -> i32 {
|
||||
physical_tile * TILE_SIZE_MULTIPLIER / 4
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn tile_drawable_irect(physical_tile: i32) -> IRect {
|
||||
let m = tile_margin_size(physical_tile);
|
||||
IRect {
|
||||
left: m,
|
||||
top: m,
|
||||
right: m + physical_tile,
|
||||
bottom: m + physical_tile,
|
||||
}
|
||||
}
|
||||
|
||||
/// GPU→GPU copy of `src` from `from` into `dst` on `to_canvas`, without
|
||||
/// `image_snapshot` (avoids per-tile sync stalls on WebGL).
|
||||
fn draw_surface_src_rect_to_dst(
|
||||
@ -50,7 +58,7 @@ fn draw_surface_src_rect_to_dst(
|
||||
to_canvas.restore();
|
||||
}
|
||||
|
||||
pub fn get_cache_size(viewbox: &Viewbox, interest: i32) -> skia::ISize {
|
||||
pub fn get_cache_size(viewbox: &Viewbox, interest: i32, physical_tile: i32) -> skia::ISize {
|
||||
// First we retrieve the extended area of the viewport that we could render.
|
||||
let TileRect(isx, isy, iex, iey) =
|
||||
tiles::get_tiles_for_viewbox_with_interest(viewbox, interest);
|
||||
@ -59,8 +67,8 @@ pub fn get_cache_size(viewbox: &Viewbox, interest: i32) -> skia::ISize {
|
||||
let dy = if isy.signum() != iey.signum() { 1 } else { 0 };
|
||||
|
||||
(
|
||||
((iex - isx).abs() + dx) * TILE_SIZE,
|
||||
((iey - isy).abs() + dy) * TILE_SIZE,
|
||||
((iex - isx).abs() + dx) * physical_tile,
|
||||
((iey - isy).abs() + dy) * physical_tile,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
@ -205,7 +213,7 @@ impl DocAtlas {
|
||||
// Keep the highest possible scale (closest to 1.0) that still fits.
|
||||
let cap = gpu_state
|
||||
.max_texture_size()
|
||||
.clamp(TILE_SIZE, DOC_ATLAS_MAX_DIM) as f32;
|
||||
.clamp(tiles::TILE_SIZE as i32, DOC_ATLAS_MAX_DIM) as f32;
|
||||
|
||||
let required_scale = (cap / doc_w).min(cap / doc_h).clamp(0.01, 1.0);
|
||||
|
||||
@ -454,6 +462,8 @@ pub struct Surfaces {
|
||||
// Tracks which surfaces have content (dirty flag bitmask)
|
||||
dirty_surfaces: u32,
|
||||
extra_tile_dims: skia::ISize,
|
||||
/// Device-pixel edge of one atlas tile (`512 × dpr`).
|
||||
physical_tile: i32,
|
||||
dpr: f32,
|
||||
}
|
||||
|
||||
@ -465,10 +475,11 @@ impl Surfaces {
|
||||
tile_dims: skia::ISize,
|
||||
) -> Result<Self> {
|
||||
let gpu_state = get_gpu_state();
|
||||
let physical_tile = tile_dims.width.max(1);
|
||||
|
||||
let extra_tile_dims = skia::ISize::new(
|
||||
tile_dims.width * TILE_SIZE_MULTIPLIER,
|
||||
tile_dims.height * TILE_SIZE_MULTIPLIER,
|
||||
physical_tile * TILE_SIZE_MULTIPLIER,
|
||||
physical_tile * TILE_SIZE_MULTIPLIER,
|
||||
);
|
||||
let margins = skia::ISize::new(extra_tile_dims.width / 4, extra_tile_dims.height / 4);
|
||||
|
||||
@ -503,9 +514,9 @@ impl Surfaces {
|
||||
let ui = gpu_state.create_surface_with_dimensions("ui".to_string(), width, height)?;
|
||||
let debug = gpu_state.create_surface_with_dimensions("debug".to_string(), width, height)?;
|
||||
|
||||
// 512, why not?
|
||||
let tiles = TileTextureCache::new(tile_atlas.width(), 512);
|
||||
let tiles = TileTextureCache::new(tile_atlas.width(), physical_tile, 512);
|
||||
let atlas = DocAtlas::try_new()?;
|
||||
let dpr = physical_tile as f32 / tiles::TILE_SIZE;
|
||||
Ok(Self {
|
||||
target,
|
||||
filter,
|
||||
@ -532,12 +543,63 @@ impl Surfaces {
|
||||
margins,
|
||||
dirty_surfaces: 0,
|
||||
extra_tile_dims,
|
||||
dpr: 1.0,
|
||||
physical_tile,
|
||||
dpr,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_dpr(&mut self, dpr: f32) {
|
||||
pub fn physical_tile_size(&self) -> i32 {
|
||||
self.physical_tile
|
||||
}
|
||||
|
||||
pub fn drawable_irect(&self) -> IRect {
|
||||
tile_drawable_irect(self.physical_tile)
|
||||
}
|
||||
|
||||
/// Recreate Current / effect / atlas-slot surfaces for a new DPR.
|
||||
/// Returns true when physical tile size changed (caller should invalidate caches).
|
||||
pub fn set_dpr(&mut self, dpr: f32) -> Result<bool> {
|
||||
self.dpr = dpr;
|
||||
let new_phys = tiles::physical_tile_size(dpr);
|
||||
if new_phys == self.physical_tile {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let gpu_state = get_gpu_state();
|
||||
let extra_tile_dims = skia::ISize::new(
|
||||
new_phys * TILE_SIZE_MULTIPLIER,
|
||||
new_phys * TILE_SIZE_MULTIPLIER,
|
||||
);
|
||||
let margins = skia::ISize::new(extra_tile_dims.width / 4, extra_tile_dims.height / 4);
|
||||
|
||||
self.filter =
|
||||
gpu_state.create_surface_with_isize("filter".to_string(), extra_tile_dims)?;
|
||||
self.current =
|
||||
gpu_state.create_surface_with_isize("current".to_string(), extra_tile_dims)?;
|
||||
self.drop_shadows =
|
||||
gpu_state.create_surface_with_isize("drop_shadows".to_string(), extra_tile_dims)?;
|
||||
self.inner_shadows =
|
||||
gpu_state.create_surface_with_isize("inner_shadows".to_string(), extra_tile_dims)?;
|
||||
self.text_drop_shadows =
|
||||
gpu_state.create_surface_with_isize("text_drop_shadows".to_string(), extra_tile_dims)?;
|
||||
self.shape_fills =
|
||||
gpu_state.create_surface_with_isize("shape_fills".to_string(), extra_tile_dims)?;
|
||||
self.shape_strokes =
|
||||
gpu_state.create_surface_with_isize("shape_strokes".to_string(), extra_tile_dims)?;
|
||||
// Keep export at least as large as the new tile work surface.
|
||||
self.export =
|
||||
gpu_state.create_surface_with_isize("export".to_string(), extra_tile_dims)?;
|
||||
|
||||
self.tiles = TileTextureCache::new(self.tile_atlas.width(), new_phys, 512);
|
||||
self.tile_atlas.canvas().clear(skia::Color::TRANSPARENT);
|
||||
self.tile_atlas_image = None;
|
||||
self.atlas.tile_doc_rects.clear();
|
||||
|
||||
self.physical_tile = new_phys;
|
||||
self.extra_tile_dims = extra_tile_dims;
|
||||
self.margins = margins;
|
||||
self.clear_all_dirty();
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn clear_tiles(&mut self) {
|
||||
@ -996,8 +1058,8 @@ impl Surfaces {
|
||||
.ok_or(Error::CriticalError("Failed to create surface".to_string()))?;
|
||||
self.cache.canvas().reset_matrix();
|
||||
self.cache.canvas().translate((
|
||||
(interest_area_threshold * TILE_SIZE) as f32,
|
||||
(interest_area_threshold * TILE_SIZE) as f32,
|
||||
(interest_area_threshold * self.physical_tile) as f32,
|
||||
(interest_area_threshold * self.physical_tile) as f32,
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
@ -1008,8 +1070,10 @@ impl Surfaces {
|
||||
cached_viewbox: &Viewbox,
|
||||
interest_area_threshold: i32,
|
||||
) -> Result<()> {
|
||||
let viewbox_cache_size = get_cache_size(viewbox, interest_area_threshold);
|
||||
let cached_viewbox_cache_size = get_cache_size(cached_viewbox, interest_area_threshold);
|
||||
let viewbox_cache_size =
|
||||
get_cache_size(viewbox, interest_area_threshold, self.physical_tile);
|
||||
let cached_viewbox_cache_size =
|
||||
get_cache_size(cached_viewbox, interest_area_threshold, self.physical_tile);
|
||||
// Only resize cache if the new size is larger than the cached size
|
||||
// This avoids unnecessary surface recreations when the cache size decreases
|
||||
if viewbox_cache_size.width > cached_viewbox_cache_size.width
|
||||
@ -1207,8 +1271,8 @@ impl Surfaces {
|
||||
/// Debug: semi-transparent tint unique per tile coords, baked into Current
|
||||
/// before atlas/backbuffer blit so tile boundaries are visible on screen.
|
||||
pub fn paint_debug_tile_overlay(&mut self, tile: &Tile) {
|
||||
let rect = skia::Rect::from(self.drawable_irect());
|
||||
let canvas = self.current.canvas();
|
||||
let rect = skia::Rect::from(TILE_DRAWABLE_RECT);
|
||||
|
||||
// Stable pseudo-random RGB from tile coords (same tile → same color).
|
||||
let h = (tile.x() as u32)
|
||||
@ -1242,7 +1306,7 @@ impl Surfaces {
|
||||
self.paint_debug_tile_overlay(tile);
|
||||
|
||||
let gpu_state = get_gpu_state();
|
||||
let src = skia::Rect::from(TILE_DRAWABLE_RECT);
|
||||
let src = skia::Rect::from(self.drawable_irect());
|
||||
let sampling = self.sampling_options;
|
||||
|
||||
// DocAtlas + tile atlas via Surface::draw (no image_snapshot sync).
|
||||
@ -1310,7 +1374,9 @@ impl Surfaces {
|
||||
let canvas = scratch.canvas();
|
||||
canvas.clear(skia::Color::TRANSPARENT);
|
||||
|
||||
let tile_size = tiles::get_tile_size(scale);
|
||||
// Grid uses zoom only; `scale` is paint scale (zoom×dpr) for screen mapping.
|
||||
let zoom = scale / self.dpr.max(0.01);
|
||||
let tile_size = tiles::get_tile_size(zoom);
|
||||
let tr = tiles::get_tiles_for_rect(src_doc_bounds, tile_size);
|
||||
let ix0 = src_irect.left as f32;
|
||||
let iy0 = src_irect.top as f32;
|
||||
@ -1319,7 +1385,7 @@ impl Surfaces {
|
||||
for ty in tr.y1()..=tr.y2() {
|
||||
for tx in tr.x1()..=tr.x2() {
|
||||
let tile = Tile(tx, ty);
|
||||
let tile_doc = tiles::get_tile_rect(tile, scale);
|
||||
let tile_doc = tiles::get_tile_rect(tile, zoom);
|
||||
let mut clip_doc = tile_doc;
|
||||
if !clip_doc.intersect(src_doc_bounds) || clip_doc.is_empty() {
|
||||
continue;
|
||||
@ -1612,11 +1678,12 @@ impl AtlasDrawBatch {
|
||||
}
|
||||
|
||||
impl TileTextureCache {
|
||||
pub fn new(texture_size: i32, capacity: usize) -> Self {
|
||||
pub fn new(texture_size: i32, physical_tile: i32, capacity: usize) -> Self {
|
||||
let physical_tile = physical_tile.max(1);
|
||||
Self {
|
||||
tile_size: tiles::TILE_SIZE,
|
||||
tile_size: physical_tile as f32,
|
||||
is_updated: false,
|
||||
provider: TileAtlasTextureProvider::new(texture_size, TILE_SIZE),
|
||||
provider: TileAtlasTextureProvider::new(texture_size, physical_tile),
|
||||
transforms: Vec::with_capacity(capacity),
|
||||
textures: Vec::with_capacity(capacity),
|
||||
grid: HashMap::with_capacity(capacity),
|
||||
@ -1737,7 +1804,7 @@ impl TileTextureCache {
|
||||
let doc_rect = tile_doc_rects
|
||||
.get(&tile)
|
||||
.copied()
|
||||
.unwrap_or_else(|| tiles::get_tile_rect(tile, s));
|
||||
.unwrap_or_else(|| tiles::get_tile_rect(tile, viewbox.zoom()));
|
||||
if doc_rect.is_empty() || !doc_rect.intersects(view_doc) {
|
||||
continue;
|
||||
}
|
||||
@ -1761,7 +1828,7 @@ impl TileTextureCache {
|
||||
let doc_rect = tile_doc_rects
|
||||
.get(&tile)
|
||||
.copied()
|
||||
.unwrap_or_else(|| tiles::get_tile_rect(tile, s));
|
||||
.unwrap_or_else(|| tiles::get_tile_rect(tile, viewbox.zoom()));
|
||||
if doc_rect.is_empty() || !doc_rect.intersects(view_doc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -31,13 +31,14 @@ impl Tile {
|
||||
)
|
||||
}
|
||||
|
||||
/// Screen-space rect for this tile using the physical tile size (512×dpr).
|
||||
#[inline(always)]
|
||||
pub fn get_rect_with_offset(&self, offset: &skia::Point) -> skia::Rect {
|
||||
pub fn get_rect_with_offset(&self, offset: &skia::Point, physical_tile_size: 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 * physical_tile_size - offset.x,
|
||||
self.1 as f32 * physical_tile_size - offset.y,
|
||||
physical_tile_size,
|
||||
physical_tile_size,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -211,9 +212,23 @@ impl TileViewbox {
|
||||
|
||||
pub const TILE_SIZE: f32 = 512.;
|
||||
|
||||
/// Document-space size of one tile. Depends only on zoom (not DPR), so the
|
||||
/// shape→tile grid stays stable across HiDPI.
|
||||
#[inline(always)]
|
||||
pub fn get_tile_dimensions() -> skia::ISize {
|
||||
(TILE_SIZE as i32, TILE_SIZE as i32).into()
|
||||
pub fn get_tile_size(zoom: f32) -> f32 {
|
||||
TILE_SIZE / zoom
|
||||
}
|
||||
|
||||
/// Physical GPU tile edge in device pixels: `512 × dpr` (ceiled).
|
||||
#[inline(always)]
|
||||
pub fn physical_tile_size(dpr: f32) -> i32 {
|
||||
(TILE_SIZE * dpr).ceil().max(1.0) as i32
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_tile_dimensions(dpr: f32) -> skia::ISize {
|
||||
let s = physical_tile_size(dpr);
|
||||
(s, s).into()
|
||||
}
|
||||
|
||||
pub fn get_tiles_for_rect(rect: skia::Rect, tile_size: f32) -> TileRect {
|
||||
@ -227,7 +242,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.zoom());
|
||||
get_tiles_for_rect(viewbox.area, tile_size)
|
||||
}
|
||||
|
||||
@ -241,20 +256,14 @@ 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, zoom: f32) -> (f32, f32) {
|
||||
let ts = get_tile_size(zoom);
|
||||
(x as f32 * ts, y as f32 * ts)
|
||||
}
|
||||
|
||||
pub fn get_tile_size(scale: f32) -> f32 {
|
||||
1. / scale * TILE_SIZE
|
||||
}
|
||||
|
||||
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, zoom: f32) -> skia::Rect {
|
||||
let (tx, ty) = get_tile_pos(tile, zoom);
|
||||
let ts = get_tile_size(zoom);
|
||||
skia::Rect::from_xywh(tx, ty, ts, ts)
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user