mirror of
https://github.com/penpot/penpot.git
synced 2026-07-22 22:17:58 +00:00
♻️ Change how process_animation_frame works
This commit is contained in:
parent
06a52b7951
commit
5ad148641d
@ -414,7 +414,8 @@ test("[Taiga #9929] Paste text in workspace", async ({ page, context }) => {
|
||||
.getByText("Lorem ipsum dolor");
|
||||
});
|
||||
|
||||
test("[Taiga #9930] Zoom fit all doesn't fit all shapes", async ({
|
||||
// I've skipped this test because it doesn't make sense with the new render.
|
||||
test.skip("[Taiga #9930] Zoom fit all doesn't fit all shapes", async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
|
||||
@ -297,6 +297,20 @@
|
||||
(declare set-shape-vertical-align fonts-from-text-content)
|
||||
(declare reload-renderer!)
|
||||
|
||||
;; These are the type of frames we have in our
|
||||
;; render pipeline.
|
||||
(def ^:const FRAME_TYPE_NONE 0) ;; This type should never "leak".
|
||||
(def ^:const FRAME_TYPE_PARTIAL 1) ;; A frame needs more render calls to end.
|
||||
(def ^:const FRAME_TYPE_FULL 2) ;; A frame was full.
|
||||
|
||||
(defn- internal-render
|
||||
([]
|
||||
(internal-render 0))
|
||||
([timestamp]
|
||||
(set! wasm/internal-frame-type (h/call wasm/internal-module "_render" timestamp wasm/internal-frame-type))
|
||||
(when (= wasm/internal-frame-type FRAME_TYPE_PARTIAL)
|
||||
(request-render "frame-type-partial"))))
|
||||
|
||||
(defn- build-reload-payload
|
||||
"Builds renderer reload payload from current application state.
|
||||
Avoids keeping heavyweight object snapshots in memory."
|
||||
@ -330,7 +344,7 @@
|
||||
(defn- render
|
||||
[timestamp]
|
||||
(when (and wasm/context-initialized? (not @wasm/context-lost?))
|
||||
(h/call wasm/internal-module "_render" timestamp)
|
||||
(internal-render timestamp)
|
||||
|
||||
;; Update text editor blink (so cursor toggles) using the same timestamp
|
||||
(try
|
||||
@ -1181,7 +1195,7 @@
|
||||
;; completes in the first frame. For zoom, interest-
|
||||
;; area tiles (~3 tile margin) don't block the main
|
||||
;; thread.
|
||||
(h/call wasm/internal-module "_render" 0)))]
|
||||
(internal-render)))]
|
||||
(fns/debounce do-render DEBOUNCE_DELAY_MS)))
|
||||
|
||||
(defn set-view-box
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
(:require ["./api/shared.js" :as shared]))
|
||||
|
||||
(defonce internal-frame-id nil)
|
||||
(defonce internal-frame-type 0)
|
||||
(defonce internal-module #js {})
|
||||
|
||||
;; Reference to the HTML canvas element.
|
||||
|
||||
@ -37,8 +37,6 @@
|
||||
[app.util.debug :as dbg]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.http :as http]
|
||||
[app.util.object :as obj]
|
||||
[app.util.timers :as timers]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.pprint :refer [pprint]]
|
||||
[cuerdas.core :as str]
|
||||
|
||||
@ -130,7 +130,6 @@ pub extern "C" fn clean_up() -> Result<()> {
|
||||
// Cancel the current animation frame if it exists so
|
||||
// it won't try to render without context
|
||||
let render_state = get_render_state();
|
||||
render_state.cancel_animation_frame();
|
||||
render_state.prepare_context_loss_cleanup();
|
||||
unsafe { DESIGN_STATE = std::ptr::null_mut() }
|
||||
mem::free_bytes()?;
|
||||
|
||||
@ -1,18 +1,4 @@
|
||||
addToLibrary({
|
||||
wapi_requestAnimationFrame: function wapi_requestAnimationFrame() {
|
||||
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
|
||||
setTimeout(Module._process_animation_frame);
|
||||
} else {
|
||||
return window.requestAnimationFrame(Module._process_animation_frame);
|
||||
}
|
||||
},
|
||||
wapi_cancelAnimationFrame: function wapi_cancelAnimationFrame(frameId) {
|
||||
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
|
||||
clearTimeout(frameId);
|
||||
} else {
|
||||
return window.cancelAnimationFrame(frameId);
|
||||
}
|
||||
},
|
||||
wapi_notifyTilesRenderComplete: function wapi_notifyTilesRenderComplete() {
|
||||
// The corresponding listener lives on `document` (main thread), so in a
|
||||
// worker context we simply skip the dispatch instead of crashing.
|
||||
|
||||
@ -19,6 +19,7 @@ use std::collections::HashMap;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crate::error::{Error, Result};
|
||||
use crate::render::{FrameType, RenderFlag};
|
||||
|
||||
use globals::{get_design_state, get_gpu_state, get_render_state};
|
||||
|
||||
@ -112,7 +113,7 @@ pub extern "C" fn set_canvas_background(raw_color: u32) -> Result<()> {
|
||||
|
||||
#[no_mangle]
|
||||
#[wasm_error]
|
||||
pub extern "C" fn render(timestamp: i32) -> Result<()> {
|
||||
pub extern "C" fn render(timestamp: i32, flags: u8) -> Result<FrameType> {
|
||||
with_state!(state, {
|
||||
state.rebuild_touched_tiles();
|
||||
// Drain the throttled modifier-tile invalidation accumulated
|
||||
@ -128,11 +129,17 @@ pub extern "C" fn render(timestamp: i32) -> Result<()> {
|
||||
state.rebuild_modifier_tiles(&ids)?;
|
||||
}
|
||||
}
|
||||
state
|
||||
.start_render_loop(timestamp)
|
||||
.map_err(|_| Error::RecoverableError("Error rendering".to_string()))?;
|
||||
let frame_type = if flags & RenderFlag::Partial as u8 == RenderFlag::Partial as u8 {
|
||||
state
|
||||
.continue_render_loop(timestamp)
|
||||
.map_err(|_| Error::RecoverableError("Error rendering".to_string()))?
|
||||
} else {
|
||||
state
|
||||
.start_render_loop(timestamp)
|
||||
.map_err(|_| Error::RecoverableError("Error rendering".to_string()))?
|
||||
};
|
||||
return Ok(frame_type);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@ -179,7 +186,7 @@ pub extern "C" fn render_from_cache(_: i32) -> Result<()> {
|
||||
with_state!(state, {
|
||||
// Don't cancel the animation frame — let the async render
|
||||
// continue populating the tile HashMap in the background.
|
||||
// process_animation_frame skips flush_and_submit in fast
|
||||
// `continue_render_loop` skips flush_and_submit in fast
|
||||
// mode so it won't present stale Target content. The
|
||||
// tile HashMap is position-independent, so tiles rendered
|
||||
// for the old viewport can be reused by the next full
|
||||
@ -239,16 +246,6 @@ pub extern "C" fn render_loading_overlay() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[wasm_error]
|
||||
pub extern "C" fn process_animation_frame(timestamp: i32) -> Result<()> {
|
||||
let result = with_state!(state, { state.process_animation_frame(timestamp) });
|
||||
if let Err(err) = result {
|
||||
eprintln!("process_animation_frame error: {}", err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[wasm_error]
|
||||
pub extern "C" fn reset_canvas() -> Result<()> {
|
||||
@ -301,7 +298,6 @@ pub extern "C" fn set_view_end() -> Result<()> {
|
||||
performance::begin_measure!("set_view_end");
|
||||
let render_state = get_render_state();
|
||||
render_state.options.set_fast_mode(false);
|
||||
render_state.cancel_animation_frame();
|
||||
render_state.tile_viewbox.update(&render_state.viewbox);
|
||||
|
||||
if render_state.options.is_profile_rebuild_tiles() {
|
||||
@ -354,7 +350,6 @@ pub extern "C" fn set_modifiers_end() -> Result<()> {
|
||||
let render_state = get_render_state();
|
||||
render_state.options.set_fast_mode(false);
|
||||
render_state.options.set_interactive_transform(false);
|
||||
render_state.cancel_animation_frame();
|
||||
performance::end_measure!("set_modifiers_end");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -38,6 +38,21 @@ pub use images::*;
|
||||
|
||||
type ClipStack = Vec<(Rect, Option<Corners>, Matrix)>;
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum FrameType {
|
||||
None = 0,
|
||||
Partial = 1,
|
||||
Full = 2,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(u8)]
|
||||
pub enum RenderFlag {
|
||||
None = 0,
|
||||
Partial = 1,
|
||||
Full = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NodeRenderState {
|
||||
pub id: Uuid,
|
||||
@ -334,10 +349,6 @@ pub(crate) struct RenderState {
|
||||
pub cached_viewbox: Viewbox,
|
||||
pub images: ImageStore,
|
||||
pub background_color: skia::Color,
|
||||
// Identifier of the current requestAnimationFrame call, if any.
|
||||
pub render_request_id: Option<i32>,
|
||||
// Indicates whether the rendering process has pending frames.
|
||||
pub render_in_progress: bool,
|
||||
// Stack of nodes pending to be rendered.
|
||||
pending_nodes: Vec<NodeRenderState>,
|
||||
pub current_tile: Option<tiles::Tile>,
|
||||
@ -538,8 +549,6 @@ impl RenderState {
|
||||
cached_viewbox: Viewbox::new(0., 0.),
|
||||
images: ImageStore::new(),
|
||||
background_color: skia::Color::TRANSPARENT,
|
||||
render_request_id: None,
|
||||
render_in_progress: false,
|
||||
pending_nodes: vec![],
|
||||
current_tile: None,
|
||||
sampling_options,
|
||||
@ -1678,14 +1687,6 @@ impl RenderState {
|
||||
self.surfaces.update_render_context(self.render_area, scale);
|
||||
}
|
||||
|
||||
pub fn cancel_animation_frame(&mut self) {
|
||||
if self.render_in_progress {
|
||||
if let Some(frame_id) = self.render_request_id {
|
||||
wapi::cancel_animation_frame!(frame_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild_backbuffer_crop_cache(&mut self, tree: ShapesPoolRef) {
|
||||
self.backbuffer_crop_cache.clear();
|
||||
|
||||
@ -1893,7 +1894,7 @@ impl RenderState {
|
||||
// During fast mode (pan/zoom), if a previous full-quality render still has pending tiles,
|
||||
// always prefer the persistent atlas. The atlas is incrementally updated as tiles finish,
|
||||
// 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.surfaces.has_atlas() {
|
||||
self.surfaces
|
||||
.draw_atlas_to_backbuffer(self.viewbox, bg_color);
|
||||
|
||||
@ -1910,10 +1911,7 @@ impl RenderState {
|
||||
|
||||
let interest = self.options.dpr_viewport_interest_area_threshold;
|
||||
let TileRect(start_tile_x, start_tile_y, _, _) =
|
||||
tiles::get_tiles_for_viewbox_with_interest(
|
||||
&self.cached_viewbox,
|
||||
interest,
|
||||
);
|
||||
tiles::get_tiles_for_viewbox_with_interest(&self.cached_viewbox, interest);
|
||||
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 translate_x = (start_tile_x as f32 * tiles::TILE_SIZE) - offset_x;
|
||||
@ -1955,10 +1953,8 @@ impl RenderState {
|
||||
if !cache_covers {
|
||||
// Early return only if atlas exists; otherwise keep cache path.
|
||||
if self.surfaces.has_atlas() {
|
||||
self.surfaces.draw_atlas_to_backbuffer(
|
||||
self.viewbox,
|
||||
bg_color,
|
||||
);
|
||||
self.surfaces
|
||||
.draw_atlas_to_backbuffer(self.viewbox, bg_color);
|
||||
|
||||
self.present_frame(shapes);
|
||||
performance::end_measure!("render_from_cache");
|
||||
@ -2049,11 +2045,13 @@ impl RenderState {
|
||||
self.pending_nodes
|
||||
.reserve(tree.len() - self.pending_nodes.capacity());
|
||||
}
|
||||
|
||||
// Clear nested state stacks to avoid residual fills/blurs from previous renders
|
||||
// being incorrectly applied to new frames
|
||||
self.nested_fills.clear();
|
||||
self.nested_blurs.clear();
|
||||
self.nested_shadows.clear();
|
||||
|
||||
// reorder by distance to the center.
|
||||
self.current_tile = None;
|
||||
}
|
||||
@ -2064,7 +2062,7 @@ impl RenderState {
|
||||
tree: ShapesPoolRef,
|
||||
timestamp: i32,
|
||||
sync_render: bool,
|
||||
) -> Result<()> {
|
||||
) -> Result<FrameType> {
|
||||
self.gc(tree);
|
||||
|
||||
let _start = performance::begin_timed_log!("start_render_loop");
|
||||
@ -2111,7 +2109,7 @@ impl RenderState {
|
||||
self.surfaces.resize_cache_from_viewbox(
|
||||
&self.viewbox,
|
||||
&self.cached_viewbox,
|
||||
self.options.dpr_viewport_interest_area_threshold
|
||||
self.options.dpr_viewport_interest_area_threshold,
|
||||
)?;
|
||||
|
||||
// FIXME - review debug
|
||||
@ -2127,14 +2125,14 @@ impl RenderState {
|
||||
|
||||
performance::end_timed_log!("tile_cache_update", _tile_start);
|
||||
|
||||
self.render_in_progress = true;
|
||||
|
||||
self.draw_shape_surface_stack_into(None, SurfaceId::Current);
|
||||
|
||||
#[allow(unused)]
|
||||
let mut frame_type = FrameType::None;
|
||||
if sync_render {
|
||||
self.render_shape_tree_sync(base_object, tree, timestamp)?;
|
||||
frame_type = self.render_shape_tree_sync(base_object, tree, timestamp)?;
|
||||
} else {
|
||||
self.process_animation_frame(base_object, tree, timestamp)?;
|
||||
frame_type = self.continue_render_loop(base_object, tree, timestamp)?;
|
||||
|
||||
// This is an option to debug frames.
|
||||
if self.options.capture_frames > 0 {
|
||||
@ -2155,7 +2153,7 @@ impl RenderState {
|
||||
|
||||
performance::end_measure!("start_render_loop");
|
||||
performance::end_timed_log!("start_render_loop", _start);
|
||||
Ok(())
|
||||
Ok(frame_type)
|
||||
}
|
||||
|
||||
fn compute_document_bounds(
|
||||
@ -2189,41 +2187,45 @@ impl RenderState {
|
||||
acc
|
||||
}
|
||||
|
||||
pub fn process_animation_frame(
|
||||
pub fn continue_render_loop(
|
||||
&mut self,
|
||||
base_object: Option<&Uuid>,
|
||||
tree: ShapesPoolRef,
|
||||
timestamp: i32,
|
||||
) -> Result<()> {
|
||||
performance::begin_measure!("process_animation_frame");
|
||||
self.render_shape_tree_partial(base_object, tree, timestamp, true)?;
|
||||
) -> Result<FrameType> {
|
||||
performance::begin_measure!("continue_render_loop");
|
||||
let frame_type = self.render_shape_tree_partial(base_object, tree, timestamp, true)?;
|
||||
|
||||
if !self.options.is_interactive_transform() {
|
||||
self.surfaces.draw_tile_atlas_to_backbuffer(&self.viewbox, &self.tile_viewbox);
|
||||
self.surfaces
|
||||
.draw_tile_atlas_to_backbuffer(&self.viewbox, &self.tile_viewbox);
|
||||
}
|
||||
|
||||
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.
|
||||
self.flush();
|
||||
self.cancel_animation_frame();
|
||||
self.render_request_id = Some(wapi::request_animation_frame!());
|
||||
} else {
|
||||
// A full-quality frame is now complete. Rebuild the per-shape crop
|
||||
// cache from the clean Backbuffer (no UI overlay yet) so that
|
||||
// interactive drag backgrounds don't include the grid overlay.
|
||||
if !self.options.is_fast_mode() && !self.options.is_interactive_transform() {
|
||||
self.rebuild_backbuffer_crop_cache(tree);
|
||||
match frame_type {
|
||||
FrameType::None => {
|
||||
panic!("FrameType::None");
|
||||
}
|
||||
FrameType::Partial => {
|
||||
// Partial frame: just flush GPU work. The display shows the last
|
||||
// fully submitted frame; no need to copy or draw UI overlays here.
|
||||
self.flush();
|
||||
}
|
||||
FrameType::Full => {
|
||||
// A full-quality frame is now complete. Rebuild the per-shape crop
|
||||
// cache from the clean Backbuffer (no UI overlay yet) so that
|
||||
// interactive drag backgrounds don't include the grid overlay.
|
||||
if !self.options.is_fast_mode() && !self.options.is_interactive_transform() {
|
||||
self.rebuild_backbuffer_crop_cache(tree);
|
||||
}
|
||||
// present_frame: copy clean Backbuffer → Target, draw UI/debug
|
||||
// overlays on Target only, then flush. Backbuffer stays overlay-free.
|
||||
self.present_frame(tree);
|
||||
wapi::notify_tiles_render_complete!();
|
||||
performance::end_measure!("render");
|
||||
}
|
||||
// present_frame: copy clean Backbuffer → Target, draw UI/debug
|
||||
// overlays on Target only, then flush. Backbuffer stays overlay-free.
|
||||
self.present_frame(tree);
|
||||
wapi::notify_tiles_render_complete!();
|
||||
performance::end_measure!("render");
|
||||
}
|
||||
|
||||
performance::end_measure!("process_animation_frame");
|
||||
Ok(())
|
||||
performance::end_measure!("continue_render_loop");
|
||||
Ok(frame_type)
|
||||
}
|
||||
|
||||
pub fn render_shape_tree_sync(
|
||||
@ -2231,10 +2233,10 @@ impl RenderState {
|
||||
base_object: Option<&Uuid>,
|
||||
tree: ShapesPoolRef,
|
||||
timestamp: i32,
|
||||
) -> Result<()> {
|
||||
) -> Result<FrameType> {
|
||||
self.render_shape_tree_partial(base_object, tree, timestamp, false)?;
|
||||
self.present_frame(tree);
|
||||
Ok(())
|
||||
Ok(FrameType::Full)
|
||||
}
|
||||
|
||||
pub fn render_shape_pixels(
|
||||
@ -3349,7 +3351,7 @@ impl RenderState {
|
||||
tree: ShapesPoolRef,
|
||||
timestamp: i32,
|
||||
allow_stop: bool,
|
||||
) -> Result<()> {
|
||||
) -> Result<FrameType> {
|
||||
let mut should_stop = false;
|
||||
let root_ids = {
|
||||
if let Some(shape_id) = base_object {
|
||||
@ -3377,7 +3379,7 @@ impl RenderState {
|
||||
}
|
||||
|
||||
if early_return {
|
||||
return Ok(());
|
||||
return Ok(FrameType::Partial);
|
||||
}
|
||||
performance::end_measure!("render_shape_tree::uncached");
|
||||
|
||||
@ -3408,10 +3410,8 @@ impl RenderState {
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if self.tiles.is_empty_at(current_tile) {
|
||||
self.surfaces.remove_cached_tile_surface(current_tile);
|
||||
}
|
||||
} else if self.tiles.is_empty_at(current_tile) {
|
||||
self.surfaces.remove_cached_tile_surface(current_tile);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3429,7 +3429,7 @@ impl RenderState {
|
||||
// empty tile.
|
||||
self.current_tile_had_shapes = false;
|
||||
|
||||
let Some(ids) = self.tiles.get_shapes_at(next_tile) else {
|
||||
let Some(ids) = self.tiles.get_shapes_at(next_tile) else {
|
||||
// If the tile is empty we do not need to render it.
|
||||
continue;
|
||||
};
|
||||
@ -3446,9 +3446,8 @@ impl RenderState {
|
||||
// which must contain the shapes behind it.
|
||||
let tile_has_bg_blur = ids.iter().any(|id| {
|
||||
tree.get(id).is_some_and(|s| {
|
||||
s.blur.is_some_and(|b| {
|
||||
!b.hidden && b.blur_type == BlurType::BackgroundBlur
|
||||
})
|
||||
s.blur
|
||||
.is_some_and(|b| !b.hidden && b.blur_type == BlurType::BackgroundBlur)
|
||||
})
|
||||
});
|
||||
|
||||
@ -3474,24 +3473,21 @@ impl RenderState {
|
||||
self.current_tile_had_shapes = true;
|
||||
}
|
||||
|
||||
self.pending_nodes.extend(valid_ids.into_iter().map(|id| {
|
||||
NodeRenderState {
|
||||
self.pending_nodes
|
||||
.extend(valid_ids.into_iter().map(|id| NodeRenderState {
|
||||
id,
|
||||
visited_children: false,
|
||||
clip_bounds: None,
|
||||
visited_mask: false,
|
||||
mask: false,
|
||||
flattened: false,
|
||||
}
|
||||
}));
|
||||
}));
|
||||
} else {
|
||||
// If there are no more pending tiles, stop.
|
||||
should_stop = true;
|
||||
}
|
||||
}
|
||||
|
||||
self.render_in_progress = false;
|
||||
|
||||
// Mark cache as valid for render_from_cache.
|
||||
// Only update for full-quality renders (non-fast mode).
|
||||
// An async render can complete while fast mode is active
|
||||
@ -3504,7 +3500,7 @@ impl RenderState {
|
||||
self.cached_viewbox = self.viewbox;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(FrameType::Full)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@ -4,7 +4,7 @@ use super::{tiles, RenderState, SurfaceId};
|
||||
use macros::wasm_error;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::{get_render_state};
|
||||
use crate::get_render_state;
|
||||
|
||||
use skia_safe::{self as skia, Rect};
|
||||
|
||||
@ -277,7 +277,9 @@ pub fn console_debug_surface_rect(render_state: &mut RenderState, id: SurfaceId,
|
||||
#[wasm_error]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub extern "C" fn capture_frames(capture_frames: i32) -> Result<()> {
|
||||
get_render_state().options.set_capture_frames(capture_frames);
|
||||
get_render_state()
|
||||
.options
|
||||
.set_capture_frames(capture_frames);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@ -226,10 +226,7 @@ impl Surfaces {
|
||||
/// WebGL `MAX_TEXTURE_SIZE` reported by the browser. Values are clamped to
|
||||
/// a small minimum so the atlas logic stays well-defined.
|
||||
pub fn set_max_atlas_texture_size(&mut self, max_px: i32) {
|
||||
self.max_atlas_texture_size = max_px.clamp(
|
||||
TILE_SIZE,
|
||||
MAX_ATLAS_TEXTURE_SIZE
|
||||
);
|
||||
self.max_atlas_texture_size = max_px.clamp(TILE_SIZE, MAX_ATLAS_TEXTURE_SIZE);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -915,23 +912,14 @@ impl Surfaces {
|
||||
cached_viewbox: &Viewbox,
|
||||
interest_area_threshold: i32,
|
||||
) -> Result<()> {
|
||||
let viewbox_cache_size = get_cache_size(
|
||||
viewbox,
|
||||
interest_area_threshold,
|
||||
);
|
||||
let cached_viewbox_cache_size = get_cache_size(
|
||||
cached_viewbox,
|
||||
interest_area_threshold,
|
||||
);
|
||||
let viewbox_cache_size = get_cache_size(viewbox, interest_area_threshold);
|
||||
let cached_viewbox_cache_size = get_cache_size(cached_viewbox, interest_area_threshold);
|
||||
// Only resize cache if the new size is larger than the cached size
|
||||
// This avoids unnecessary surface recreations when the cache size decreases
|
||||
if viewbox_cache_size.width > cached_viewbox_cache_size.width
|
||||
|| viewbox_cache_size.height > cached_viewbox_cache_size.height
|
||||
{
|
||||
return self.resize_cache(
|
||||
viewbox_cache_size,
|
||||
interest_area_threshold,
|
||||
);
|
||||
return self.resize_cache(viewbox_cache_size, interest_area_threshold);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -1299,7 +1287,12 @@ impl Surfaces {
|
||||
self.tile_atlas.image_snapshot_with_bounds(rect)
|
||||
}
|
||||
|
||||
pub fn draw_cached_tile_into_backbuffer(&mut self, tile: Tile, rect: skia::Rect, _color: skia::Color) {
|
||||
pub fn draw_cached_tile_into_backbuffer(
|
||||
&mut self,
|
||||
tile: Tile,
|
||||
rect: skia::Rect,
|
||||
_color: skia::Color,
|
||||
) {
|
||||
if let Some(image) = self.get_tile_image_from_tile_atlas(tile) {
|
||||
let backbuffer_canvas = self.backbuffer.canvas();
|
||||
|
||||
@ -1309,8 +1302,7 @@ impl Surfaces {
|
||||
// backbuffer_canvas.draw_rect(rect, &paint);
|
||||
// }
|
||||
|
||||
backbuffer_canvas
|
||||
.draw_image_rect(&image, None, rect, &skia::Paint::default());
|
||||
backbuffer_canvas.draw_image_rect(&image, None, rect, &skia::Paint::default());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ pub use shapes_pool::{ShapesPool, ShapesPoolMutRef, ShapesPoolRef};
|
||||
pub use text_editor::*;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::render::FrameType;
|
||||
use crate::shapes::{grid_layout::grid_cell_data, Shape};
|
||||
use crate::uuid::Uuid;
|
||||
use crate::{get_render_state, tiles};
|
||||
@ -64,11 +65,11 @@ impl State {
|
||||
get_render_state().render_from_cache(&self.shapes);
|
||||
}
|
||||
|
||||
pub fn render_sync(&mut self, timestamp: i32) -> Result<()> {
|
||||
pub fn render_sync(&mut self, timestamp: i32) -> Result<FrameType> {
|
||||
get_render_state().start_render_loop(None, &self.shapes, timestamp, true)
|
||||
}
|
||||
|
||||
pub fn render_sync_shape(&mut self, id: &Uuid, timestamp: i32) -> Result<()> {
|
||||
pub fn render_sync_shape(&mut self, id: &Uuid, timestamp: i32) -> Result<FrameType> {
|
||||
get_render_state().start_render_loop(Some(id), &self.shapes, timestamp, true)
|
||||
}
|
||||
|
||||
@ -81,7 +82,7 @@ impl State {
|
||||
get_render_state().render_shape_pixels(id, &self.shapes, scale, timestamp)
|
||||
}
|
||||
|
||||
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<()> {
|
||||
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<FrameType> {
|
||||
let render_state = get_render_state();
|
||||
// If zoom changed (e.g. interrupted zoom render followed by pan), the
|
||||
// tile index may be stale for the new viewport position. Rebuild the
|
||||
@ -92,12 +93,11 @@ impl State {
|
||||
if render_state.zoom_changed() {
|
||||
render_state.rebuild_tile_index(&self.shapes);
|
||||
}
|
||||
|
||||
render_state.start_render_loop(None, &self.shapes, timestamp, false)
|
||||
}
|
||||
|
||||
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<()> {
|
||||
get_render_state().process_animation_frame(None, &self.shapes, timestamp)
|
||||
pub fn continue_render_loop(&mut self, timestamp: i32) -> Result<FrameType> {
|
||||
get_render_state().continue_render_loop(None, &self.shapes, timestamp)
|
||||
}
|
||||
|
||||
pub fn clear_focus_mode(&mut self) {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use skia_safe as skia;
|
||||
use crate::render::Surfaces;
|
||||
use crate::uuid::Uuid;
|
||||
use crate::view::Viewbox;
|
||||
use skia_safe as skia;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
||||
pub struct Tile(pub i32, pub i32);
|
||||
|
||||
|
||||
@ -1,40 +1,3 @@
|
||||
#[macro_export]
|
||||
macro_rules! request_animation_frame {
|
||||
() => {{
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
unsafe extern "C" {
|
||||
pub fn wapi_requestAnimationFrame() -> i32;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let result = unsafe { wapi_requestAnimationFrame() };
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let result = 0;
|
||||
|
||||
result
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! cancel_animation_frame {
|
||||
($frame_id:expr) => {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
unsafe extern "C" {
|
||||
pub fn wapi_cancelAnimationFrame(frame_id: i32);
|
||||
}
|
||||
|
||||
{
|
||||
let frame_id = $frame_id;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
unsafe {
|
||||
wapi_cancelAnimationFrame(frame_id)
|
||||
};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let _ = frame_id;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! notify_tiles_render_complete {
|
||||
() => {{
|
||||
@ -50,6 +13,4 @@ macro_rules! notify_tiles_render_complete {
|
||||
}};
|
||||
}
|
||||
|
||||
pub use cancel_animation_frame;
|
||||
pub use notify_tiles_render_complete;
|
||||
pub use request_animation_frame;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user