mirror of
https://github.com/penpot/penpot.git
synced 2026-07-24 15:08:24 +00:00
🎉 Add draw_atlas alternative to draw tiles
This commit is contained in:
parent
f38d068a62
commit
dd9d183958
@ -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();
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user