From dd9d183958c2acbaa005ae0f93fbed307abbd92f Mon Sep 17 00:00:00 2001 From: Aitor Moreno Date: Wed, 13 May 2026 14:36:46 +0200 Subject: [PATCH] :tada: Add draw_atlas alternative to draw tiles --- render-wasm/src/main.rs | 6 +- render-wasm/src/render.rs | 89 +++------- render-wasm/src/render/surfaces.rs | 270 +++++++++++++++++++++++++---- render-wasm/src/tiles.rs | 82 +++++---- render-wasm/src/view.rs | 20 ++- 5 files changed, 320 insertions(+), 147 deletions(-) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index c1ebc94839..df3e17495b 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -302,11 +302,7 @@ pub extern "C" fn set_view_end() -> Result<()> { let render_state = get_render_state(); render_state.options.set_fast_mode(false); render_state.cancel_animation_frame(); - - let scale = render_state.get_scale(); - render_state - .tile_viewbox - .update(render_state.viewbox, scale); + render_state.tile_viewbox.update(render_state.viewbox); if render_state.options.is_profile_rebuild_tiles() { state.rebuild_tiles(); diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 2cd5dcd460..794a0bcfab 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -370,7 +370,7 @@ pub(crate) struct RenderState { /// Cleared at the beginning of a render pass; set to true after we clear Cache the first /// time we are about to blit a tile into Cache for this pass. pub cache_cleared_this_render: bool, - /// True iff the current tile had shapes assigned to it when we + /// True if the current tile had shapes assigned to it when we /// started rendering it. Lets us distinguish a genuinely empty /// tile (skip composite, just clear) from a tile whose walker /// finished its work in a previous PAF and is now being resumed @@ -441,10 +441,10 @@ fn drag_crop_snapshot_window_px( (ox, oy, win_w, win_h) } -pub fn get_cache_size(viewbox: Viewbox, scale: f32, interest: i32) -> skia::ISize { +pub fn get_cache_size(viewbox: Viewbox, interest: 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, scale); + tiles::get_tiles_for_viewbox_with_interest(viewbox, interest); let dx = if isx.signum() != iex.signum() { 1 } else { 0 }; let dy = if isy.signum() != iey.signum() { 1 } else { 0 }; @@ -565,7 +565,6 @@ impl RenderState { tile_viewbox: tiles::TileViewbox::new_with_interest( viewbox, options.dpr_viewport_interest_area_threshold, - 1.0, ), pending_tiles: PendingTiles::new(), nested_fills: vec![], @@ -791,6 +790,8 @@ impl RenderState { self.viewbox.height().floor() as i32, )?; self.fonts.set_scale_debug_font(dpr); + self.viewbox.set_dpr(dpr); + self.surfaces.set_dpr(dpr); } Ok(()) } @@ -836,7 +837,7 @@ impl RenderState { let dpr_height = (height as f32 * self.options.dpr).floor() as i32; self.surfaces.resize(dpr_width, dpr_height)?; self.viewbox.set_wh(width as f32, height as f32); - self.tile_viewbox.update(self.viewbox, self.get_scale()); + self.tile_viewbox.update(self.viewbox); Ok(()) } @@ -1901,8 +1902,6 @@ impl RenderState { pub fn render_from_cache(&mut self, shapes: ShapesPoolRef) { let _start = performance::begin_timed_log!("render_from_cache"); performance::begin_measure!("render_from_cache"); - let cached_scale = self.get_cached_scale(); - let bg_color = self.background_color; // During fast mode (pan/zoom), if a previous full-quality render still has pending tiles, @@ -1910,7 +1909,7 @@ impl RenderState { // and drawing from it avoids mixing a partially-updated Cache surface with missing tiles. if self.options.is_fast_mode() && self.render_in_progress && self.surfaces.has_atlas() { self.surfaces - .draw_atlas_to_backbuffer(self.viewbox, self.options.dpr, bg_color); + .draw_atlas_to_backbuffer(self.viewbox, bg_color); self.present_frame(shapes); performance::end_measure!("render_from_cache"); @@ -1928,7 +1927,6 @@ impl RenderState { tiles::get_tiles_for_viewbox_with_interest( self.cached_viewbox, interest, - cached_scale, ); let offset_x = self.viewbox.area.left * self.cached_viewbox.zoom * self.options.dpr; let offset_y = self.viewbox.area.top * self.cached_viewbox.zoom * self.options.dpr; @@ -1973,7 +1971,6 @@ impl RenderState { if self.surfaces.has_atlas() { self.surfaces.draw_atlas_to_backbuffer( self.viewbox, - self.options.dpr, bg_color, ); @@ -2008,18 +2005,15 @@ impl RenderState { // would be at wrong positions — skip them and let the full // render after set_view_end handle it. if !self.zoom_changed() { - let current_scale = self.get_scale(); - let visible_rect = tiles::get_tiles_for_viewbox(self.viewbox, current_scale); - let vb_offset_x = self.viewbox.area.left * current_scale; - let vb_offset_y = self.viewbox.area.top * current_scale; - + let visible_rect = tiles::get_tiles_for_viewbox(self.viewbox); + let offset = self.viewbox.get_offset(); for tx in visible_rect.x1()..=visible_rect.x2() { for ty in visible_rect.y1()..=visible_rect.y2() { let tile = tiles::Tile::from(tx, ty); if self.surfaces.has_cached_tile_surface(tile) { let tile_rect = skia::Rect::from_xywh( - tx as f32 * tiles::TILE_SIZE - vb_offset_x, - ty as f32 * tiles::TILE_SIZE - vb_offset_y, + tx as f32 * tiles::TILE_SIZE - offset.x, + ty as f32 * tiles::TILE_SIZE - offset.y, tiles::TILE_SIZE, tiles::TILE_SIZE, ); @@ -2076,7 +2070,7 @@ impl RenderState { let _start = performance::begin_timed_log!("start_render_loop"); let scale = self.get_scale(); - self.tile_viewbox.update(self.viewbox, scale); + self.tile_viewbox.update(self.viewbox); self.focus_mode.reset(); performance::begin_measure!("render"); @@ -2115,12 +2109,10 @@ impl RenderState { let viewbox_cache_size = get_cache_size( self.viewbox, - scale, self.options.dpr_viewport_interest_area_threshold, ); let cached_viewbox_cache_size = get_cache_size( self.cached_viewbox, - scale, self.options.dpr_viewport_interest_area_threshold, ); // Only resize cache if the new size is larger than the cached size @@ -2223,6 +2215,9 @@ impl RenderState { performance::begin_measure!("process_animation_frame"); self.render_shape_tree_partial(base_object, tree, timestamp, true)?; + self.surfaces.draw_tile_atlas_to_backbuffer(&self.viewbox, &self.tile_viewbox); + self.flush_and_submit(); + if self.render_in_progress { // Partial frame: just flush GPU work. The display shows the last // fully submitted frame; no need to copy or draw UI overlays here. @@ -3384,47 +3379,9 @@ impl RenderState { while !should_stop { if let Some(current_tile) = self.current_tile { - if self.surfaces.has_cached_tile_surface(current_tile) { - performance::begin_measure!("render_shape_tree::cached"); - // During interactive transforms, `Target` is preserved and seeded once - // from Backbuffer. Cached tiles are therefore already visible and - // re-blitting them costs extra GPU work. - let tile_rect = self.get_current_tile_bounds()?; - if !self.options.is_interactive_transform() { - self.surfaces.draw_cached_tile_surface( - current_tile, - tile_rect, - self.background_color, - ); - } - - // Also draw the cached tile to the Cache surface so - // render_from_cache (used during pan) has the full scene. - // apply_render_to_final_canvas clears Cache on the first - // uncached tile, but cached tiles must also be present. - if !self.options.is_fast_mode() { - if !self.cache_cleared_this_render { - self.surfaces.clear_cache(self.background_color); - self.cache_cleared_this_render = true; - } - let aligned_rect = self.get_aligned_tile_bounds(current_tile); - self.surfaces.draw_cached_tile_to_cache( - current_tile, - &aligned_rect, - self.background_color, - ); - } - performance::end_measure!("render_shape_tree::cached"); - - if self.options.is_debug_visible() { - debug::render_workspace_current_tile( - self, - "Cached".to_string(), - current_tile, - tile_rect, - ); - } - } else { + // NOTA: Ahora no tenemos que cubrir el caso en el que el tile + // no está cacheado porque se hará todo desde el draw_atlas. + if !self.surfaces.has_cached_tile_surface(current_tile) { performance::begin_measure!("render_shape_tree::uncached"); let (is_empty, early_return) = self .render_shape_tree_partial_uncached(tree, timestamp, allow_stop, false)?; @@ -3464,6 +3421,10 @@ impl RenderState { // Tile is uncached and has no shapes to render self.apply_render_to_final_canvas(tile_rect)?; } + } else { + if self.tiles.is_empty_at(current_tile) { + self.surfaces.remove_cached_tile_surface(current_tile); + } } } @@ -3881,11 +3842,7 @@ impl RenderState { if let Some((_, export_scale)) = self.export_context { return export_scale; } - self.viewbox.zoom() * self.options.dpr - } - - pub fn get_cached_scale(&self) -> f32 { - self.cached_viewbox.zoom() * self.options.dpr + self.viewbox.get_scale() } pub fn zoom_changed(&self) -> bool { diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index 2b9973a2e8..ef3d6e6de2 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -3,12 +3,10 @@ use crate::shapes::Shape; use crate::view::Viewbox; use crate::{get_gpu_state, performance}; -use skia_safe::{self as skia, IRect, Paint, RRect}; +use skia_safe::{self as skia, IRect, Paint, RRect, Rect}; -use super::{ - gpu_state::GpuState, - tiles::{self, Tile, TileViewbox, TILE_SIZE}, -}; +use super::{gpu_state::GpuState, tiles, tiles::Tile, tiles::TileViewbox, tiles::TILE_SIZE}; +use crate::math::Point; use base64::{engine::general_purpose, Engine as _}; use std::collections::{HashMap, HashSet}; @@ -37,6 +35,7 @@ pub enum DrawOnCache { #[repr(u32)] #[derive(Debug, PartialEq, Clone, Copy)] +#[allow(unused)] pub enum SurfaceId { Target = 0b000_0000_0001, Filter = 0b000_0000_0010, @@ -52,6 +51,7 @@ pub enum SurfaceId { Debug = 0b100_0000_0001, Atlas = 0b100_0000_0010, Backbuffer = 0b100_0000_0100, + TileAtlas = 0b100_0000_1000, } pub struct Surfaces { @@ -79,6 +79,8 @@ pub struct Surfaces { export: skia::Surface, // Persistent viewport-sized surface used to keep the last presented frame. backbuffer: skia::Surface, + // Atlas used to keep tiles. + tile_atlas: skia::Surface, tiles: TileTextureCache, // Persistent 1:1 document-space atlas that gets incrementally updated as tiles render. @@ -103,8 +105,8 @@ pub struct Surfaces { pub margins: skia::ISize, // Tracks which surfaces have content (dirty flag bitmask) dirty_surfaces: u32, - extra_tile_dims: skia::ISize, + dpr: f32, } #[allow(dead_code)] @@ -127,6 +129,16 @@ impl Surfaces { let cache = gpu_state.create_surface_with_dimensions("cache".to_string(), width, height)?; let backbuffer = gpu_state.create_surface_with_dimensions("backbuffer".to_string(), width, height)?; + + // TODO: Obtener este tamaño de alguna otra parte. + let max_texture_size = 8192; + // NOTA: Esta textura debería utilizar el máximo permitido por la GPU. + let tile_atlas = gpu_state.create_surface_with_dimensions( + "tile_atlas".to_string(), + max_texture_size, + max_texture_size, + )?; + let current = gpu_state.create_surface_with_isize("current".to_string(), extra_tile_dims)?; @@ -149,7 +161,8 @@ impl Surfaces { let mut atlas = gpu_state.create_surface_with_dimensions("atlas".to_string(), 1, 1)?; atlas.canvas().clear(skia::Color::TRANSPARENT); - let tiles = TileTextureCache::new(); + // 512, why not? + let tiles = TileTextureCache::new(tile_atlas.width(), 512); Ok(Self { target, filter, @@ -164,6 +177,7 @@ impl Surfaces { debug, export, backbuffer, + tile_atlas, tiles, atlas, atlas_origin: skia::Point::new(0.0, 0.0), @@ -176,9 +190,14 @@ impl Surfaces { margins, dirty_surfaces: 0, extra_tile_dims, + dpr: 1.0, }) } + pub fn set_dpr(&mut self, dpr: f32) { + self.dpr = dpr; + } + /// Sets the maximum atlas texture dimension (one side). Should match the /// WebGL `MAX_TEXTURE_SIZE` reported by the browser. Values are clamped to /// a small minimum so the atlas logic stays well-defined. @@ -471,16 +490,28 @@ impl Surfaces { self.atlas_size.width > 0 && self.atlas_size.height > 0 } + pub fn draw_tile_atlas_to_backbuffer(&mut self, viewbox: &Viewbox, tile_viewbox: &TileViewbox) { + let sampling_options = + skia::SamplingOptions::new(skia::FilterMode::Nearest, skia::MipmapMode::None); + + self.tiles.update(viewbox, tile_viewbox); + self.backbuffer.canvas().draw_atlas( + &self.tile_atlas.image_snapshot(), + &self.tiles.transforms, + &self.tiles.textures, + None, + skia::BlendMode::SrcOver, + sampling_options, + None, + None, + ); + } + /// Draw the persistent atlas onto the backbuffer using the current viewbox transform. /// Intended for fast pan/zoom-out previews (avoids per-tile composition). /// Clears Backbuffer to `background` first so atlas-uncovered regions don't /// show stale content when the atlas only partially covers the viewport. - pub fn draw_atlas_to_backbuffer( - &mut self, - viewbox: Viewbox, - dpr: f32, - background: skia::Color, - ) { + pub fn draw_atlas_to_backbuffer(&mut self, viewbox: Viewbox, background: skia::Color) { if !self.has_atlas() { return; } @@ -758,6 +789,7 @@ impl Surfaces { SurfaceId::UI => &mut self.ui, SurfaceId::Export => &mut self.export, SurfaceId::Atlas => &mut self.atlas, + SurfaceId::TileAtlas => &mut self.tile_atlas, } } @@ -778,6 +810,7 @@ impl Surfaces { SurfaceId::UI => &self.ui, SurfaceId::Export => &self.export, SurfaceId::Atlas => &self.atlas, + SurfaceId::TileAtlas => &self.tile_atlas, } } @@ -1054,7 +1087,14 @@ impl Surfaces { // `tile_doc_rect` is in world/document coordinates (1 unit == 1 px at 100%). let _ = self.blit_tile_image_into_atlas(gpu_state, &tile_image, tile_doc_rect); self.atlas_tile_doc_rects.insert(*tile, tile_doc_rect); - self.tiles.add(tile_viewbox, tile, tile_image); + + let tile_ref = self.tiles.add(tile_viewbox, tile); + self.tile_atlas.canvas().draw_image_rect( + &tile_image, + None, + tile_ref.rect, + &skia::Paint::default(), + ); } } @@ -1131,7 +1171,17 @@ impl Surfaces { (clip_doc.bottom - vb_top) * scale - iy0, ); - if let Some(tile_image) = self.tiles.get(tile) { + if let Some(tile_ref) = self.tiles.get(tile) { + let bounds = skia::IRect::from_ltrb( + tile_ref.rect.left as i32, + tile_ref.rect.top as i32, + tile_ref.rect.right as i32, + tile_ref.rect.bottom as i32, + ); + let Some(tile_image) = self.tile_atlas.image_snapshot_with_bounds(bounds) + else { + panic!("Cannot retrieve tile image"); + }; let iw = tile_image.width() as f32; let ih = tile_image.height() as f32; let td_w = tile_doc.width().max(1e-6); @@ -1185,13 +1235,25 @@ impl Surfaces { let _ = self.clear_tile_in_atlas(gpu_state, tile); } + pub fn get_tile_image_from_tile_atlas(&mut self, tile: Tile) -> Option { + let Some(tile_ref) = self.tiles.get(tile) else { + panic!("Tile not found {}:{}", tile.0, tile.1); + }; + + let rect = IRect::from_ltrb( + tile_ref.rect.left as i32, + tile_ref.rect.top as i32, + tile_ref.rect.right as i32, + tile_ref.rect.bottom as i32, + ); + self.tile_atlas.image_snapshot_with_bounds(rect) + } + pub fn draw_cached_tile_surface(&mut self, tile: Tile, rect: skia::Rect, color: skia::Color) { - if let Some(image) = self.tiles.get(tile) { + if let Some(image) = self.get_tile_image_from_tile_atlas(tile) { let mut paint = skia::Paint::default(); paint.set_color(color); - self.backbuffer.canvas().draw_rect(rect, &paint); - self.backbuffer .canvas() .draw_image_rect(&image, None, rect, &skia::Paint::default()); @@ -1208,7 +1270,7 @@ impl Surfaces { aligned_rect: &skia::Rect, color: skia::Color, ) { - if let Some(image) = self.tiles.get(tile) { + if let Some(image) = self.get_tile_image_from_tile_atlas(tile) { let mut bg = skia::Paint::default(); bg.set_color(color); self.cache.canvas().draw_rect(aligned_rect, &bg); @@ -1347,31 +1409,103 @@ impl Surfaces { } } +#[derive(Debug, Clone)] +pub struct TileAtlasTextureRef { + pub index: usize, + pub rect: skia::Rect, +} + +impl TileAtlasTextureRef { + pub fn new(index: usize, rect: skia::Rect) -> Self { + Self { index, rect } + } +} + +pub struct TileAtlasTextureProvider { + pub index: usize, + pub length: usize, + pub in_use: Vec, + pub rects: Vec, +} + +impl TileAtlasTextureProvider { + pub fn new(texture_size: i32, tile_size: i32) -> Self { + let side = texture_size / tile_size; + let length = side * side; + let mut rects = Vec::with_capacity(length as usize); + for i in 0..length { + let left = (i % side) as f32 * tile_size as f32; + let top = (i / side) as f32 * tile_size as f32; + let right = left + tile_size as f32; + let bottom = top + tile_size as f32; + rects.push(Rect::new(left, top, right, bottom)); + } + Self { + index: 0, + length: length as usize, + in_use: vec![false; length as usize], + rects, + } + } + + pub fn allocate(&mut self) -> Option { + let start = self.index; + loop { + if !self.in_use[self.index] { + self.in_use[self.index] = true; + return Some(TileAtlasTextureRef::new(self.index, self.rects[self.index])); + } + + self.index = (self.index + 1) % self.length; + if self.index == start { + return None; + } + } + } + + pub fn deallocate(&mut self, reference: TileAtlasTextureRef) -> bool { + // In this case the user of the provider it's trying to release + // a reference already freed. + if !self.in_use[reference.index] { + return false; + } + self.in_use[reference.index] = false; + self.index = reference.index; + true + } +} + pub struct TileTextureCache { - grid: HashMap, + tile_size: f32, + provider: TileAtlasTextureProvider, + transforms: Vec, + textures: Vec, + grid: HashMap, removed: HashSet, } impl TileTextureCache { - pub fn new() -> Self { + pub fn new(texture_size: i32, capacity: usize) -> Self { Self { - grid: HashMap::default(), - removed: HashSet::default(), + tile_size: TILE_SIZE, + provider: TileAtlasTextureProvider::new(texture_size, TILE_SIZE as i32), + transforms: Vec::with_capacity(capacity), + textures: Vec::with_capacity(capacity), + grid: HashMap::with_capacity(capacity), + removed: HashSet::with_capacity(capacity), } } - pub fn has(&self, tile: Tile) -> bool { - self.grid.contains_key(&tile) && !self.removed.contains(&tile) - } - fn gc(&mut self) { // Make a real remove for tile in self.removed.iter() { - self.grid.remove(tile); + if let Some(tile_ref) = self.grid.remove(tile) { + self.provider.deallocate(tile_ref); + } } } - fn free_tiles(&mut self, tile_viewbox: &TileViewbox) { + fn gc_non_visible(&mut self, tile_viewbox: &TileViewbox) { let marked: Vec<_> = self .grid .iter_mut() @@ -1386,35 +1520,99 @@ impl TileTextureCache { .collect(); for tile in marked.iter() { - self.grid.remove(tile); + if let Some(tile_ref) = self.grid.remove(tile) { + self.provider.deallocate(tile_ref); + } } } - pub fn add(&mut self, tile_viewbox: &TileViewbox, tile: &Tile, image: skia::Image) { + pub fn update(&mut self, viewbox: &Viewbox, tile_viewbox: &TileViewbox) { + if self.transforms.len() != tile_viewbox.visible_rect.len() as usize { + self.transforms.resize( + tile_viewbox.visible_rect.len() as usize, + skia::RSXform::new(1.0, 0.0, Point::default()), + ); + } + + if self.textures.len() != tile_viewbox.visible_rect.len() as usize { + self.textures.resize( + tile_viewbox.visible_rect.len() as usize, + skia::Rect::new_empty(), + ); + } + + for texture in self.textures.iter_mut() { + texture.set_empty(); + } + + let offset = viewbox.get_offset(); + let mut index = 0; + for y in tile_viewbox.visible_rect.top()..=tile_viewbox.visible_rect.bottom() { + for x in tile_viewbox.visible_rect.left()..=tile_viewbox.visible_rect.right() { + let tile = Tile(x, y); + + let Some(tile_ref) = self.grid.get(&tile) else { + continue; + }; + + self.transforms[index].tx = x as f32 * self.tile_size - offset.x; + self.transforms[index].ty = y as f32 * self.tile_size - offset.y; + + self.textures[index].set_ltrb( + tile_ref.rect.left, + tile_ref.rect.top, + tile_ref.rect.right, + tile_ref.rect.bottom, + ); + + index += 1; + } + } + } + + pub fn has(&self, tile: Tile) -> bool { + self.grid.contains_key(&tile) && !self.removed.contains(&tile) + } + + pub fn add(&mut self, tile_viewbox: &TileViewbox, tile: &Tile) -> TileAtlasTextureRef { if self.grid.len() > TEXTURES_CACHE_CAPACITY { - // First we try to remove the obsolete tiles + // First we try to remove the obsolete tiles. self.gc(); } + // If we still have a texture capacity problem, then + // we try to remove all of those tiles that aren't + // visible. if self.grid.len() > TEXTURES_CACHE_CAPACITY { - self.free_tiles(tile_viewbox); + self.gc_non_visible(tile_viewbox); } - self.grid.insert(*tile, image); + let Some(tile_ref) = self.provider.allocate() else { + panic!("Tile texture allocation failed {}:{}", tile.0, tile.1); + }; + + self.grid.insert(*tile, tile_ref.clone()); if self.removed.contains(tile) { self.removed.remove(tile); } + + tile_ref.clone() } - pub fn get(&mut self, tile: Tile) -> Option<&mut skia::Image> { + pub fn get(&mut self, tile: Tile) -> Option<&TileAtlasTextureRef> { if self.removed.contains(&tile) { return None; } - self.grid.get_mut(&tile) + self.grid.get(&tile) } pub fn remove(&mut self, tile: Tile) { + if let Some(tile_ref) = self.grid.get(&tile) { + if tile_ref.index < self.textures.len() { + self.textures[tile_ref.index].set_empty(); + } + } self.removed.insert(tile); } diff --git a/render-wasm/src/tiles.rs b/render-wasm/src/tiles.rs index 7c078ad2e6..676a75249c 100644 --- a/render-wasm/src/tiles.rs +++ b/render-wasm/src/tiles.rs @@ -21,87 +21,98 @@ impl Tile { #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] pub struct TileRect(pub i32, pub i32, pub i32, pub i32); +#[allow(dead_code)] impl TileRect { pub fn empty() -> Self { Self(0, 0, 0, 0) } - #[inline] + #[inline(always)] + pub fn is_degenerate(&self) -> bool { + self.left() > self.right() || self.top() > self.bottom() + } + + #[inline(always)] + pub fn len(&self) -> i32 { + (self.width() + 1) * (self.height() + 1) + } + + #[inline(always)] pub fn x1(&self) -> i32 { self.0 } - #[inline] + #[inline(always)] pub fn y1(&self) -> i32 { self.1 } - #[inline] + #[inline(always)] pub fn x2(&self) -> i32 { self.2 } - #[inline] + #[inline(always)] pub fn y2(&self) -> i32 { self.3 } - #[inline] + #[inline(always)] pub fn left(&self) -> i32 { self.0 } - #[inline] + #[inline(always)] pub fn top(&self) -> i32 { self.1 } - #[inline] + #[inline(always)] pub fn right(&self) -> i32 { self.2 } - #[inline] + #[inline(always)] pub fn bottom(&self) -> i32 { self.3 } - #[inline] + #[inline(always)] pub fn x(&self) -> i32 { self.0 } - #[inline] + #[inline(always)] pub fn y(&self) -> i32 { self.1 } - #[inline] + #[inline(always)] pub fn width(&self) -> i32 { self.x2() - self.x1() } - #[inline] + #[inline(always)] pub fn half_width(&self) -> i32 { self.width() / 2 } - #[inline] + #[inline(always)] pub fn height(&self) -> i32 { self.y2() - self.y1() } - #[inline] + #[inline(always)] pub fn half_height(&self) -> i32 { self.height() / 2 } - #[inline] + #[inline(always)] pub fn center_x(&self) -> i32 { self.x() + self.half_width() } - #[inline] + #[inline(always)] pub fn center_y(&self) -> i32 { self.y() + self.half_height() } @@ -123,19 +134,19 @@ pub struct TileViewbox { } impl TileViewbox { - pub fn new_with_interest(viewbox: Viewbox, interest: i32, scale: f32) -> Self { + pub fn new_with_interest(viewbox: Viewbox, interest: i32) -> Self { Self { - visible_rect: get_tiles_for_viewbox(viewbox, scale), - interest_rect: get_tiles_for_viewbox_with_interest(viewbox, interest, scale), + visible_rect: get_tiles_for_viewbox(viewbox), + interest_rect: get_tiles_for_viewbox_with_interest(viewbox, interest), interest, - center: get_tile_center_for_viewbox(viewbox, scale), + center: get_tile_center_for_viewbox(viewbox), } } - pub fn update(&mut self, viewbox: Viewbox, scale: f32) { - self.visible_rect = get_tiles_for_viewbox(viewbox, scale); - self.interest_rect = get_tiles_for_viewbox_with_interest(viewbox, self.interest, scale); - self.center = get_tile_center_for_viewbox(viewbox, scale); + pub fn update(&mut self, viewbox: Viewbox) { + self.visible_rect = get_tiles_for_viewbox(viewbox); + self.interest_rect = get_tiles_for_viewbox_with_interest(viewbox, self.interest); + self.center = get_tile_center_for_viewbox(viewbox); } pub fn set_interest(&mut self, interest: i32) { @@ -164,22 +175,18 @@ pub fn get_tiles_for_rect(rect: skia::Rect, tile_size: f32) -> TileRect { TileRect(sx, sy, ex, ey) } -pub fn get_tiles_for_viewbox(viewbox: Viewbox, scale: f32) -> TileRect { - let tile_size = get_tile_size(scale); +pub fn get_tiles_for_viewbox(viewbox: Viewbox) -> TileRect { + let tile_size = get_tile_size(viewbox.get_scale()); get_tiles_for_rect(viewbox.area, tile_size) } -pub fn get_tiles_for_viewbox_with_interest( - viewbox: Viewbox, - interest: i32, - scale: f32, -) -> TileRect { - let TileRect(sx, sy, ex, ey) = get_tiles_for_viewbox(viewbox, scale); +pub fn get_tiles_for_viewbox_with_interest(viewbox: Viewbox, interest: i32) -> TileRect { + let TileRect(sx, sy, ex, ey) = get_tiles_for_viewbox(viewbox); TileRect(sx - interest, sy - interest, ex + interest, ey + interest) } -pub fn get_tile_center_for_viewbox(viewbox: Viewbox, scale: f32) -> Tile { - let TileRect(sx, sy, ex, ey) = get_tiles_for_viewbox(viewbox, scale); +pub fn get_tile_center_for_viewbox(viewbox: Viewbox) -> Tile { + let TileRect(sx, sy, ex, ey) = get_tiles_for_viewbox(viewbox); Tile((ex - sx) / 2, (ey - sy) / 2) } @@ -214,6 +221,13 @@ impl TileHashMap { } } + pub fn is_empty_at(&self, tile: Tile) -> bool { + if let Some(uuids) = self.grid.get(&tile) { + return uuids.is_empty(); + } + true + } + pub fn get_shapes_at(&mut self, tile: Tile) -> Option<&HashSet> { self.grid.get(&tile) } diff --git a/render-wasm/src/view.rs b/render-wasm/src/view.rs index 6840c3ff60..efa5394012 100644 --- a/render-wasm/src/view.rs +++ b/render-wasm/src/view.rs @@ -1,5 +1,5 @@ -use std::ops::{Mul}; use crate::math::{Matrix, Point, Rect, Size}; +use std::ops::Mul; #[derive(Debug, Copy, Clone)] pub(crate) struct Viewbox { @@ -22,6 +22,7 @@ impl Default for Viewbox { } } +#[allow(dead_code)] impl Viewbox { pub fn new(width: f32, height: f32) -> Self { let size = Size::new(width, height); @@ -49,11 +50,6 @@ impl Viewbox { self.size.height } - pub fn set_dpr(&mut self, dpr: f32) -> f32 { - self.dpr = dpr; - dpr - } - pub fn set_all(&mut self, zoom: f32, pan_x: f32, pan_y: f32) { self.pan.set(pan_x, pan_y); self.zoom = zoom; @@ -71,6 +67,18 @@ impl Viewbox { .set_wh(self.size.width / self.zoom, self.size.height / self.zoom); } + pub fn set_dpr(&mut self, dpr: f32) { + self.dpr = dpr; + } + + pub fn get_scale(&self) -> f32 { + self.zoom * self.dpr + } + + pub fn get_offset(&self) -> Point { + self.area.tl().mul(self.get_scale()) + } + pub fn pan(&self) -> Point { self.pan }