🎉 Add draw_atlas alternative to draw tiles

This commit is contained in:
Aitor Moreno 2026-05-13 14:36:46 +02:00
parent f38d068a62
commit dd9d183958
5 changed files with 320 additions and 147 deletions

View File

@ -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();

View File

@ -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 {

View File

@ -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<skia::Image> {
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<bool>,
pub rects: Vec<Rect>,
}
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<TileAtlasTextureRef> {
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, skia::Image>,
tile_size: f32,
provider: TileAtlasTextureProvider,
transforms: Vec<skia::RSXform>,
textures: Vec<skia::Rect>,
grid: HashMap<Tile, TileAtlasTextureRef>,
removed: HashSet<Tile>,
}
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);
}

View File

@ -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<Uuid>> {
self.grid.get(&tile)
}

View File

@ -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
}