🎉 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(); let render_state = get_render_state();
render_state.options.set_fast_mode(false); render_state.options.set_fast_mode(false);
render_state.cancel_animation_frame(); render_state.cancel_animation_frame();
render_state.tile_viewbox.update(render_state.viewbox);
let scale = render_state.get_scale();
render_state
.tile_viewbox
.update(render_state.viewbox, scale);
if render_state.options.is_profile_rebuild_tiles() { if render_state.options.is_profile_rebuild_tiles() {
state.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 /// 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. /// time we are about to blit a tile into Cache for this pass.
pub cache_cleared_this_render: bool, 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 /// started rendering it. Lets us distinguish a genuinely empty
/// tile (skip composite, just clear) from a tile whose walker /// tile (skip composite, just clear) from a tile whose walker
/// finished its work in a previous PAF and is now being resumed /// 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) (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. // First we retrieve the extended area of the viewport that we could render.
let TileRect(isx, isy, iex, iey) = 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 dx = if isx.signum() != iex.signum() { 1 } else { 0 };
let dy = if isy.signum() != iey.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( tile_viewbox: tiles::TileViewbox::new_with_interest(
viewbox, viewbox,
options.dpr_viewport_interest_area_threshold, options.dpr_viewport_interest_area_threshold,
1.0,
), ),
pending_tiles: PendingTiles::new(), pending_tiles: PendingTiles::new(),
nested_fills: vec![], nested_fills: vec![],
@ -791,6 +790,8 @@ impl RenderState {
self.viewbox.height().floor() as i32, self.viewbox.height().floor() as i32,
)?; )?;
self.fonts.set_scale_debug_font(dpr); self.fonts.set_scale_debug_font(dpr);
self.viewbox.set_dpr(dpr);
self.surfaces.set_dpr(dpr);
} }
Ok(()) Ok(())
} }
@ -836,7 +837,7 @@ impl RenderState {
let dpr_height = (height as f32 * self.options.dpr).floor() as i32; let dpr_height = (height as f32 * self.options.dpr).floor() as i32;
self.surfaces.resize(dpr_width, dpr_height)?; self.surfaces.resize(dpr_width, dpr_height)?;
self.viewbox.set_wh(width as f32, height as f32); 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(()) Ok(())
} }
@ -1901,8 +1902,6 @@ impl RenderState {
pub fn render_from_cache(&mut self, shapes: ShapesPoolRef) { pub fn render_from_cache(&mut self, shapes: ShapesPoolRef) {
let _start = performance::begin_timed_log!("render_from_cache"); let _start = performance::begin_timed_log!("render_from_cache");
performance::begin_measure!("render_from_cache"); performance::begin_measure!("render_from_cache");
let cached_scale = self.get_cached_scale();
let bg_color = self.background_color; let bg_color = self.background_color;
// During fast mode (pan/zoom), if a previous full-quality render still has pending tiles, // 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. // 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() { if self.options.is_fast_mode() && self.render_in_progress && self.surfaces.has_atlas() {
self.surfaces 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); self.present_frame(shapes);
performance::end_measure!("render_from_cache"); performance::end_measure!("render_from_cache");
@ -1928,7 +1927,6 @@ impl RenderState {
tiles::get_tiles_for_viewbox_with_interest( tiles::get_tiles_for_viewbox_with_interest(
self.cached_viewbox, self.cached_viewbox,
interest, interest,
cached_scale,
); );
let offset_x = self.viewbox.area.left * self.cached_viewbox.zoom * self.options.dpr; 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; 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() { if self.surfaces.has_atlas() {
self.surfaces.draw_atlas_to_backbuffer( self.surfaces.draw_atlas_to_backbuffer(
self.viewbox, self.viewbox,
self.options.dpr,
bg_color, bg_color,
); );
@ -2008,18 +2005,15 @@ impl RenderState {
// would be at wrong positions — skip them and let the full // would be at wrong positions — skip them and let the full
// render after set_view_end handle it. // render after set_view_end handle it.
if !self.zoom_changed() { if !self.zoom_changed() {
let current_scale = self.get_scale(); let visible_rect = tiles::get_tiles_for_viewbox(self.viewbox);
let visible_rect = tiles::get_tiles_for_viewbox(self.viewbox, current_scale); let offset = self.viewbox.get_offset();
let vb_offset_x = self.viewbox.area.left * current_scale;
let vb_offset_y = self.viewbox.area.top * current_scale;
for tx in visible_rect.x1()..=visible_rect.x2() { for tx in visible_rect.x1()..=visible_rect.x2() {
for ty in visible_rect.y1()..=visible_rect.y2() { for ty in visible_rect.y1()..=visible_rect.y2() {
let tile = tiles::Tile::from(tx, ty); let tile = tiles::Tile::from(tx, ty);
if self.surfaces.has_cached_tile_surface(tile) { if self.surfaces.has_cached_tile_surface(tile) {
let tile_rect = skia::Rect::from_xywh( let tile_rect = skia::Rect::from_xywh(
tx as f32 * tiles::TILE_SIZE - vb_offset_x, tx as f32 * tiles::TILE_SIZE - offset.x,
ty as f32 * tiles::TILE_SIZE - vb_offset_y, ty as f32 * tiles::TILE_SIZE - offset.y,
tiles::TILE_SIZE, tiles::TILE_SIZE,
tiles::TILE_SIZE, tiles::TILE_SIZE,
); );
@ -2076,7 +2070,7 @@ impl RenderState {
let _start = performance::begin_timed_log!("start_render_loop"); let _start = performance::begin_timed_log!("start_render_loop");
let scale = self.get_scale(); let scale = self.get_scale();
self.tile_viewbox.update(self.viewbox, scale); self.tile_viewbox.update(self.viewbox);
self.focus_mode.reset(); self.focus_mode.reset();
performance::begin_measure!("render"); performance::begin_measure!("render");
@ -2115,12 +2109,10 @@ impl RenderState {
let viewbox_cache_size = get_cache_size( let viewbox_cache_size = get_cache_size(
self.viewbox, self.viewbox,
scale,
self.options.dpr_viewport_interest_area_threshold, self.options.dpr_viewport_interest_area_threshold,
); );
let cached_viewbox_cache_size = get_cache_size( let cached_viewbox_cache_size = get_cache_size(
self.cached_viewbox, self.cached_viewbox,
scale,
self.options.dpr_viewport_interest_area_threshold, self.options.dpr_viewport_interest_area_threshold,
); );
// Only resize cache if the new size is larger than the cached size // 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"); performance::begin_measure!("process_animation_frame");
self.render_shape_tree_partial(base_object, tree, timestamp, true)?; 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 { if self.render_in_progress {
// Partial frame: just flush GPU work. The display shows the last // Partial frame: just flush GPU work. The display shows the last
// fully submitted frame; no need to copy or draw UI overlays here. // fully submitted frame; no need to copy or draw UI overlays here.
@ -3384,47 +3379,9 @@ impl RenderState {
while !should_stop { while !should_stop {
if let Some(current_tile) = self.current_tile { if let Some(current_tile) = self.current_tile {
if self.surfaces.has_cached_tile_surface(current_tile) { // NOTA: Ahora no tenemos que cubrir el caso en el que el tile
performance::begin_measure!("render_shape_tree::cached"); // no está cacheado porque se hará todo desde el draw_atlas.
// During interactive transforms, `Target` is preserved and seeded once if !self.surfaces.has_cached_tile_surface(current_tile) {
// 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 {
performance::begin_measure!("render_shape_tree::uncached"); performance::begin_measure!("render_shape_tree::uncached");
let (is_empty, early_return) = self let (is_empty, early_return) = self
.render_shape_tree_partial_uncached(tree, timestamp, allow_stop, false)?; .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 // Tile is uncached and has no shapes to render
self.apply_render_to_final_canvas(tile_rect)?; 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 { if let Some((_, export_scale)) = self.export_context {
return export_scale; return export_scale;
} }
self.viewbox.zoom() * self.options.dpr self.viewbox.get_scale()
}
pub fn get_cached_scale(&self) -> f32 {
self.cached_viewbox.zoom() * self.options.dpr
} }
pub fn zoom_changed(&self) -> bool { pub fn zoom_changed(&self) -> bool {

View File

@ -3,12 +3,10 @@ use crate::shapes::Shape;
use crate::view::Viewbox; use crate::view::Viewbox;
use crate::{get_gpu_state, performance}; 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::{ use super::{gpu_state::GpuState, tiles, tiles::Tile, tiles::TileViewbox, tiles::TILE_SIZE};
gpu_state::GpuState, use crate::math::Point;
tiles::{self, Tile, TileViewbox, TILE_SIZE},
};
use base64::{engine::general_purpose, Engine as _}; use base64::{engine::general_purpose, Engine as _};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -37,6 +35,7 @@ pub enum DrawOnCache {
#[repr(u32)] #[repr(u32)]
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone, Copy)]
#[allow(unused)]
pub enum SurfaceId { pub enum SurfaceId {
Target = 0b000_0000_0001, Target = 0b000_0000_0001,
Filter = 0b000_0000_0010, Filter = 0b000_0000_0010,
@ -52,6 +51,7 @@ pub enum SurfaceId {
Debug = 0b100_0000_0001, Debug = 0b100_0000_0001,
Atlas = 0b100_0000_0010, Atlas = 0b100_0000_0010,
Backbuffer = 0b100_0000_0100, Backbuffer = 0b100_0000_0100,
TileAtlas = 0b100_0000_1000,
} }
pub struct Surfaces { pub struct Surfaces {
@ -79,6 +79,8 @@ pub struct Surfaces {
export: skia::Surface, export: skia::Surface,
// Persistent viewport-sized surface used to keep the last presented frame. // Persistent viewport-sized surface used to keep the last presented frame.
backbuffer: skia::Surface, backbuffer: skia::Surface,
// Atlas used to keep tiles.
tile_atlas: skia::Surface,
tiles: TileTextureCache, tiles: TileTextureCache,
// Persistent 1:1 document-space atlas that gets incrementally updated as tiles render. // Persistent 1:1 document-space atlas that gets incrementally updated as tiles render.
@ -103,8 +105,8 @@ pub struct Surfaces {
pub margins: skia::ISize, pub margins: skia::ISize,
// Tracks which surfaces have content (dirty flag bitmask) // Tracks which surfaces have content (dirty flag bitmask)
dirty_surfaces: u32, dirty_surfaces: u32,
extra_tile_dims: skia::ISize, extra_tile_dims: skia::ISize,
dpr: f32,
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -127,6 +129,16 @@ impl Surfaces {
let cache = gpu_state.create_surface_with_dimensions("cache".to_string(), width, height)?; let cache = gpu_state.create_surface_with_dimensions("cache".to_string(), width, height)?;
let backbuffer = let backbuffer =
gpu_state.create_surface_with_dimensions("backbuffer".to_string(), width, height)?; 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 = let current =
gpu_state.create_surface_with_isize("current".to_string(), extra_tile_dims)?; 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)?; let mut atlas = gpu_state.create_surface_with_dimensions("atlas".to_string(), 1, 1)?;
atlas.canvas().clear(skia::Color::TRANSPARENT); atlas.canvas().clear(skia::Color::TRANSPARENT);
let tiles = TileTextureCache::new(); // 512, why not?
let tiles = TileTextureCache::new(tile_atlas.width(), 512);
Ok(Self { Ok(Self {
target, target,
filter, filter,
@ -164,6 +177,7 @@ impl Surfaces {
debug, debug,
export, export,
backbuffer, backbuffer,
tile_atlas,
tiles, tiles,
atlas, atlas,
atlas_origin: skia::Point::new(0.0, 0.0), atlas_origin: skia::Point::new(0.0, 0.0),
@ -176,9 +190,14 @@ impl Surfaces {
margins, margins,
dirty_surfaces: 0, dirty_surfaces: 0,
extra_tile_dims, 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 /// Sets the maximum atlas texture dimension (one side). Should match the
/// WebGL `MAX_TEXTURE_SIZE` reported by the browser. Values are clamped to /// WebGL `MAX_TEXTURE_SIZE` reported by the browser. Values are clamped to
/// a small minimum so the atlas logic stays well-defined. /// 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 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. /// Draw the persistent atlas onto the backbuffer using the current viewbox transform.
/// Intended for fast pan/zoom-out previews (avoids per-tile composition). /// Intended for fast pan/zoom-out previews (avoids per-tile composition).
/// Clears Backbuffer to `background` first so atlas-uncovered regions don't /// Clears Backbuffer to `background` first so atlas-uncovered regions don't
/// show stale content when the atlas only partially covers the viewport. /// show stale content when the atlas only partially covers the viewport.
pub fn draw_atlas_to_backbuffer( pub fn draw_atlas_to_backbuffer(&mut self, viewbox: Viewbox, background: skia::Color) {
&mut self,
viewbox: Viewbox,
dpr: f32,
background: skia::Color,
) {
if !self.has_atlas() { if !self.has_atlas() {
return; return;
} }
@ -758,6 +789,7 @@ impl Surfaces {
SurfaceId::UI => &mut self.ui, SurfaceId::UI => &mut self.ui,
SurfaceId::Export => &mut self.export, SurfaceId::Export => &mut self.export,
SurfaceId::Atlas => &mut self.atlas, SurfaceId::Atlas => &mut self.atlas,
SurfaceId::TileAtlas => &mut self.tile_atlas,
} }
} }
@ -778,6 +810,7 @@ impl Surfaces {
SurfaceId::UI => &self.ui, SurfaceId::UI => &self.ui,
SurfaceId::Export => &self.export, SurfaceId::Export => &self.export,
SurfaceId::Atlas => &self.atlas, 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%). // `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); 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.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, (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 iw = tile_image.width() as f32;
let ih = tile_image.height() as f32; let ih = tile_image.height() as f32;
let td_w = tile_doc.width().max(1e-6); let td_w = tile_doc.width().max(1e-6);
@ -1185,13 +1235,25 @@ impl Surfaces {
let _ = self.clear_tile_in_atlas(gpu_state, tile); 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) { 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(); let mut paint = skia::Paint::default();
paint.set_color(color); paint.set_color(color);
self.backbuffer.canvas().draw_rect(rect, &paint); self.backbuffer.canvas().draw_rect(rect, &paint);
self.backbuffer self.backbuffer
.canvas() .canvas()
.draw_image_rect(&image, None, rect, &skia::Paint::default()); .draw_image_rect(&image, None, rect, &skia::Paint::default());
@ -1208,7 +1270,7 @@ impl Surfaces {
aligned_rect: &skia::Rect, aligned_rect: &skia::Rect,
color: skia::Color, 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(); let mut bg = skia::Paint::default();
bg.set_color(color); bg.set_color(color);
self.cache.canvas().draw_rect(aligned_rect, &bg); 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 { 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>, removed: HashSet<Tile>,
} }
impl TileTextureCache { impl TileTextureCache {
pub fn new() -> Self { pub fn new(texture_size: i32, capacity: usize) -> Self {
Self { Self {
grid: HashMap::default(), tile_size: TILE_SIZE,
removed: HashSet::default(), 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) { fn gc(&mut self) {
// Make a real remove // Make a real remove
for tile in self.removed.iter() { 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 let marked: Vec<_> = self
.grid .grid
.iter_mut() .iter_mut()
@ -1386,35 +1520,99 @@ impl TileTextureCache {
.collect(); .collect();
for tile in marked.iter() { 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 { 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(); 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 { 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) { if self.removed.contains(tile) {
self.removed.remove(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) { if self.removed.contains(&tile) {
return None; return None;
} }
self.grid.get_mut(&tile) self.grid.get(&tile)
} }
pub fn remove(&mut self, tile: 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); self.removed.insert(tile);
} }

View File

@ -21,87 +21,98 @@ impl Tile {
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
pub struct TileRect(pub i32, pub i32, pub i32, pub i32); pub struct TileRect(pub i32, pub i32, pub i32, pub i32);
#[allow(dead_code)]
impl TileRect { impl TileRect {
pub fn empty() -> Self { pub fn empty() -> Self {
Self(0, 0, 0, 0) 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 { pub fn x1(&self) -> i32 {
self.0 self.0
} }
#[inline] #[inline(always)]
pub fn y1(&self) -> i32 { pub fn y1(&self) -> i32 {
self.1 self.1
} }
#[inline] #[inline(always)]
pub fn x2(&self) -> i32 { pub fn x2(&self) -> i32 {
self.2 self.2
} }
#[inline] #[inline(always)]
pub fn y2(&self) -> i32 { pub fn y2(&self) -> i32 {
self.3 self.3
} }
#[inline] #[inline(always)]
pub fn left(&self) -> i32 { pub fn left(&self) -> i32 {
self.0 self.0
} }
#[inline] #[inline(always)]
pub fn top(&self) -> i32 { pub fn top(&self) -> i32 {
self.1 self.1
} }
#[inline] #[inline(always)]
pub fn right(&self) -> i32 { pub fn right(&self) -> i32 {
self.2 self.2
} }
#[inline] #[inline(always)]
pub fn bottom(&self) -> i32 { pub fn bottom(&self) -> i32 {
self.3 self.3
} }
#[inline] #[inline(always)]
pub fn x(&self) -> i32 { pub fn x(&self) -> i32 {
self.0 self.0
} }
#[inline] #[inline(always)]
pub fn y(&self) -> i32 { pub fn y(&self) -> i32 {
self.1 self.1
} }
#[inline] #[inline(always)]
pub fn width(&self) -> i32 { pub fn width(&self) -> i32 {
self.x2() - self.x1() self.x2() - self.x1()
} }
#[inline] #[inline(always)]
pub fn half_width(&self) -> i32 { pub fn half_width(&self) -> i32 {
self.width() / 2 self.width() / 2
} }
#[inline] #[inline(always)]
pub fn height(&self) -> i32 { pub fn height(&self) -> i32 {
self.y2() - self.y1() self.y2() - self.y1()
} }
#[inline] #[inline(always)]
pub fn half_height(&self) -> i32 { pub fn half_height(&self) -> i32 {
self.height() / 2 self.height() / 2
} }
#[inline] #[inline(always)]
pub fn center_x(&self) -> i32 { pub fn center_x(&self) -> i32 {
self.x() + self.half_width() self.x() + self.half_width()
} }
#[inline] #[inline(always)]
pub fn center_y(&self) -> i32 { pub fn center_y(&self) -> i32 {
self.y() + self.half_height() self.y() + self.half_height()
} }
@ -123,19 +134,19 @@ pub struct TileViewbox {
} }
impl 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 { Self {
visible_rect: get_tiles_for_viewbox(viewbox, scale), visible_rect: get_tiles_for_viewbox(viewbox),
interest_rect: get_tiles_for_viewbox_with_interest(viewbox, interest, scale), interest_rect: get_tiles_for_viewbox_with_interest(viewbox, interest),
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) { pub fn update(&mut self, viewbox: Viewbox) {
self.visible_rect = get_tiles_for_viewbox(viewbox, scale); self.visible_rect = get_tiles_for_viewbox(viewbox);
self.interest_rect = get_tiles_for_viewbox_with_interest(viewbox, self.interest, scale); self.interest_rect = get_tiles_for_viewbox_with_interest(viewbox, self.interest);
self.center = get_tile_center_for_viewbox(viewbox, scale); self.center = get_tile_center_for_viewbox(viewbox);
} }
pub fn set_interest(&mut self, interest: i32) { 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) TileRect(sx, sy, ex, ey)
} }
pub fn get_tiles_for_viewbox(viewbox: Viewbox, scale: f32) -> TileRect { pub fn get_tiles_for_viewbox(viewbox: Viewbox) -> TileRect {
let tile_size = get_tile_size(scale); let tile_size = get_tile_size(viewbox.get_scale());
get_tiles_for_rect(viewbox.area, tile_size) get_tiles_for_rect(viewbox.area, tile_size)
} }
pub fn get_tiles_for_viewbox_with_interest( pub fn get_tiles_for_viewbox_with_interest(viewbox: Viewbox, interest: i32) -> TileRect {
viewbox: Viewbox, let TileRect(sx, sy, ex, ey) = get_tiles_for_viewbox(viewbox);
interest: i32,
scale: f32,
) -> TileRect {
let TileRect(sx, sy, ex, ey) = get_tiles_for_viewbox(viewbox, scale);
TileRect(sx - interest, sy - interest, ex + interest, ey + interest) TileRect(sx - interest, sy - interest, ex + interest, ey + interest)
} }
pub fn get_tile_center_for_viewbox(viewbox: Viewbox, scale: f32) -> Tile { pub fn get_tile_center_for_viewbox(viewbox: Viewbox) -> Tile {
let TileRect(sx, sy, ex, ey) = get_tiles_for_viewbox(viewbox, scale); let TileRect(sx, sy, ex, ey) = get_tiles_for_viewbox(viewbox);
Tile((ex - sx) / 2, (ey - sy) / 2) 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>> { pub fn get_shapes_at(&mut self, tile: Tile) -> Option<&HashSet<Uuid>> {
self.grid.get(&tile) self.grid.get(&tile)
} }

View File

@ -1,5 +1,5 @@
use std::ops::{Mul};
use crate::math::{Matrix, Point, Rect, Size}; use crate::math::{Matrix, Point, Rect, Size};
use std::ops::Mul;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub(crate) struct Viewbox { pub(crate) struct Viewbox {
@ -22,6 +22,7 @@ impl Default for Viewbox {
} }
} }
#[allow(dead_code)]
impl Viewbox { impl Viewbox {
pub fn new(width: f32, height: f32) -> Self { pub fn new(width: f32, height: f32) -> Self {
let size = Size::new(width, height); let size = Size::new(width, height);
@ -49,11 +50,6 @@ impl Viewbox {
self.size.height 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) { pub fn set_all(&mut self, zoom: f32, pan_x: f32, pan_y: f32) {
self.pan.set(pan_x, pan_y); self.pan.set(pan_x, pan_y);
self.zoom = zoom; self.zoom = zoom;
@ -71,6 +67,18 @@ impl Viewbox {
.set_wh(self.size.width / self.zoom, self.size.height / self.zoom); .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 { pub fn pan(&self) -> Point {
self.pan self.pan
} }