mirror of
https://github.com/penpot/penpot.git
synced 2026-09-09 13:39:02 +00:00
♻️ Refactor render pipeline (#9891)
* ♻️ Refactor viewbox * 🎉 Add draw_atlas alternative to draw tiles * 🐛 Fix minor glitches * ♻️ Change how process_animation_frame works * ♻️ Refactor document atlas * ♻️ Refactor max texture size * ♻️ Refactor entrypoints and dead_code
This commit is contained in:
parent
7bf519a127
commit
d0f6d5b3a1
@ -414,7 +414,8 @@ test("[Taiga #9929] Paste text in workspace", async ({ page, context }) => {
|
|||||||
.getByText("Lorem ipsum dolor");
|
.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,
|
page,
|
||||||
context,
|
context,
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@ -306,6 +306,20 @@
|
|||||||
(declare set-shape-vertical-align fonts-from-text-content)
|
(declare set-shape-vertical-align fonts-from-text-content)
|
||||||
(declare reload-renderer!)
|
(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
|
(defn- build-reload-payload
|
||||||
"Builds renderer reload payload from current application state.
|
"Builds renderer reload payload from current application state.
|
||||||
Avoids keeping heavyweight object snapshots in memory."
|
Avoids keeping heavyweight object snapshots in memory."
|
||||||
@ -337,8 +351,8 @@
|
|||||||
;; This should never be called from the outside.
|
;; This should never be called from the outside.
|
||||||
(defn- render
|
(defn- render
|
||||||
[timestamp]
|
[timestamp]
|
||||||
(when (initialized?)
|
(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
|
;; Update text editor blink (so cursor toggles) using the same timestamp
|
||||||
(try
|
(try
|
||||||
@ -1201,7 +1215,7 @@
|
|||||||
;; completes in the first frame. For zoom, interest-
|
;; completes in the first frame. For zoom, interest-
|
||||||
;; area tiles (~3 tile margin) don't block the main
|
;; area tiles (~3 tile margin) don't block the main
|
||||||
;; thread.
|
;; thread.
|
||||||
(h/call wasm/internal-module "_render" 0)))]
|
(internal-render)))]
|
||||||
(fns/debounce do-render DEBOUNCE_DELAY_MS)))
|
(fns/debounce do-render DEBOUNCE_DELAY_MS)))
|
||||||
|
|
||||||
(defn set-view-box
|
(defn set-view-box
|
||||||
@ -1804,50 +1818,19 @@
|
|||||||
(contains? cf/flags :render-wasm-info)
|
(contains? cf/flags :render-wasm-info)
|
||||||
(bit-or 2r00000000000000000000000000001000)))
|
(bit-or 2r00000000000000000000000000001000)))
|
||||||
|
|
||||||
(defn- wasm-aa-threshold-from-route-params
|
(defn- wasm-get-numeric-value
|
||||||
"Reads optional `aa_threshold` query param from the router"
|
[name]
|
||||||
[]
|
|
||||||
(when-let [raw (let [p (rt/get-params @st/state)]
|
(when-let [raw (let [p (rt/get-params @st/state)]
|
||||||
(:aa_threshold p))]
|
(get p name))]
|
||||||
(let [n (if (string? raw) (js/parseFloat raw) raw)]
|
(let [n (if (string? raw) (js/parseFloat raw) raw)]
|
||||||
(when (and (number? n) (not (js/isNaN n)) (pos? n))
|
(when (and (number? n) (not (js/isNaN n)) (pos? n))
|
||||||
n))))
|
n))))
|
||||||
|
|
||||||
(defn- wasm-blur-downscale-threshold-from-route-params
|
(defn- wasm-set-param-from-route-params-if-present
|
||||||
"Reads optional `aa_threshold` query param from the router"
|
[param-name]
|
||||||
[]
|
(when-let [value (wasm-get-numeric-value param-name)]
|
||||||
(when-let [raw (let [p (rt/get-params @st/state)]
|
(let [setter-name (str/concat "_set_" (name param-name))]
|
||||||
(:blur_downscale_threshold p))]
|
(h/call wasm/internal-module setter-name value))))
|
||||||
(let [n (if (string? raw) (js/parseFloat raw) raw)]
|
|
||||||
(when (and (number? n) (not (js/isNaN n)) (pos? n))
|
|
||||||
n))))
|
|
||||||
|
|
||||||
(defn- wasm-max-blocking-time-ms-from-route-params
|
|
||||||
"Reads optional `aa_threshold` query param from the router"
|
|
||||||
[]
|
|
||||||
(when-let [raw (let [p (rt/get-params @st/state)]
|
|
||||||
(:max_blocking_time_ms p))]
|
|
||||||
(let [n (if (string? raw) (js/parseInt raw 10) raw)]
|
|
||||||
(when (and (number? n) (not (js/isNaN n)) (pos? n))
|
|
||||||
n))))
|
|
||||||
|
|
||||||
(defn- wasm-node-batch-threshold-from-route-params
|
|
||||||
"Reads optional `aa_threshold` query param from the router"
|
|
||||||
[]
|
|
||||||
(when-let [raw (let [p (rt/get-params @st/state)]
|
|
||||||
(:node_batch_threshold p))]
|
|
||||||
(let [n (if (string? raw) (js/parseInt raw 10) raw)]
|
|
||||||
(when (and (number? n) (not (js/isNaN n)) (pos? n))
|
|
||||||
n))))
|
|
||||||
|
|
||||||
(defn- wasm-viewport-interest-area-threshold-from-route-params
|
|
||||||
"Reads optional `aa_threshold` query param from the router"
|
|
||||||
[]
|
|
||||||
(when-let [raw (let [p (rt/get-params @st/state)]
|
|
||||||
(:viewport_interest_area_threshold p))]
|
|
||||||
(let [n (if (string? raw) (js/parseInt raw 10) raw)]
|
|
||||||
(when (and (number? n) (not (js/isNaN n)) (pos? n))
|
|
||||||
n))))
|
|
||||||
|
|
||||||
(defn set-canvas-size
|
(defn set-canvas-size
|
||||||
[canvas]
|
[canvas]
|
||||||
@ -1914,18 +1897,13 @@
|
|||||||
;; Initialize Wasm Render Engine
|
;; Initialize Wasm Render Engine
|
||||||
(h/call wasm/internal-module "_init" (/ (.-width ^js canvas) dpr) (/ (.-height ^js canvas) dpr))
|
(h/call wasm/internal-module "_init" (/ (.-width ^js canvas) dpr) (/ (.-height ^js canvas) dpr))
|
||||||
(h/call wasm/internal-module "_set_render_options" flags dpr)
|
(h/call wasm/internal-module "_set_render_options" flags dpr)
|
||||||
(when-let [t (wasm-aa-threshold-from-route-params)]
|
|
||||||
(h/call wasm/internal-module "_set_antialias_threshold" t))
|
;; Configurable parameters.
|
||||||
(when-let [t (wasm-viewport-interest-area-threshold-from-route-params)]
|
(wasm-set-param-from-route-params-if-present :antialias_threshold)
|
||||||
(h/call wasm/internal-module "_set_viewport_interest_area_threshold" t))
|
(wasm-set-param-from-route-params-if-present :viewport_interest_area_threshold)
|
||||||
(when-let [t (wasm-max-blocking-time-ms-from-route-params)]
|
(wasm-set-param-from-route-params-if-present :max_blocking_time_ms)
|
||||||
(h/call wasm/internal-module "_set_max_blocking_time_ms" t))
|
(wasm-set-param-from-route-params-if-present :node_batch_threshold)
|
||||||
(when-let [t (wasm-node-batch-threshold-from-route-params)]
|
(wasm-set-param-from-route-params-if-present :blur_downscale_threshold)
|
||||||
(h/call wasm/internal-module "_set_node_batch_threshold" t))
|
|
||||||
(when-let [t (wasm-blur-downscale-threshold-from-route-params)]
|
|
||||||
(h/call wasm/internal-module "_set_blur_downscale_threshold" t))
|
|
||||||
(when-let [max-tex (webgl/max-texture-size context)]
|
|
||||||
(h/call wasm/internal-module "_set_max_atlas_texture_size" max-tex))
|
|
||||||
|
|
||||||
;; Set browser and canvas size only after initialization
|
;; Set browser and canvas size only after initialization
|
||||||
(h/call wasm/internal-module "_set_browser" browser)
|
(h/call wasm/internal-module "_set_browser" browser)
|
||||||
|
|||||||
@ -11,15 +11,6 @@
|
|||||||
[app.render-wasm.wasm :as wasm]
|
[app.render-wasm.wasm :as wasm]
|
||||||
[promesa.core :as p]))
|
[promesa.core :as p]))
|
||||||
|
|
||||||
(defn max-texture-size
|
|
||||||
"Returns `gl.MAX_TEXTURE_SIZE` (max dimension of a 2D texture), or nil if
|
|
||||||
unavailable."
|
|
||||||
[gl]
|
|
||||||
(when gl
|
|
||||||
(let [n (.getParameter ^js gl (.-MAX_TEXTURE_SIZE ^js gl))]
|
|
||||||
(when (and (number? n) (pos? n) (js/isFinite n))
|
|
||||||
(js/Math.floor n)))))
|
|
||||||
|
|
||||||
(defn get-webgl-context
|
(defn get-webgl-context
|
||||||
"Gets the WebGL context from the WASM module"
|
"Gets the WebGL context from the WASM module"
|
||||||
[]
|
[]
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
(:require ["./api/shared.js" :as shared]))
|
(:require ["./api/shared.js" :as shared]))
|
||||||
|
|
||||||
(defonce internal-frame-id nil)
|
(defonce internal-frame-id nil)
|
||||||
|
(defonce internal-frame-type 0)
|
||||||
(defonce internal-module #js {})
|
(defonce internal-module #js {})
|
||||||
|
|
||||||
;; Reference to the HTML canvas element.
|
;; Reference to the HTML canvas element.
|
||||||
|
|||||||
@ -37,8 +37,6 @@
|
|||||||
[app.util.debug :as dbg]
|
[app.util.debug :as dbg]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.http :as http]
|
[app.util.http :as http]
|
||||||
[app.util.object :as obj]
|
|
||||||
[app.util.timers :as timers]
|
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[cljs.pprint :refer [pprint]]
|
[cljs.pprint :refer [pprint]]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
@ -135,6 +133,14 @@
|
|||||||
(wasm.mem/free)
|
(wasm.mem/free)
|
||||||
text)))
|
text)))
|
||||||
|
|
||||||
|
(defn ^:export wasmCaptureFrames
|
||||||
|
[amount]
|
||||||
|
(let [module wasm/internal-module
|
||||||
|
f (when module (unchecked-get module "_capture_frames"))]
|
||||||
|
(if (fn? f)
|
||||||
|
(wasm.h/call module "_capture_frames" amount)
|
||||||
|
(js/console.warn "[debug] render-wasm module not ready or missing _render_stats"))))
|
||||||
|
|
||||||
(defn ^:export wasmRenderStats
|
(defn ^:export wasmRenderStats
|
||||||
[]
|
[]
|
||||||
(let [module wasm/internal-module
|
(let [module wasm/internal-module
|
||||||
@ -213,31 +219,6 @@
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
")
|
")
|
||||||
|
|
||||||
(defn ^:export fps
|
|
||||||
"Adds a widget to keep track of the average FPS's"
|
|
||||||
[]
|
|
||||||
(let [last (volatile! (.now js/performance))
|
|
||||||
avg (volatile! 0)
|
|
||||||
node (-> (.createElement js/document "div")
|
|
||||||
(obj/set! "id" "fps")
|
|
||||||
(obj/set! "style" widget-style))
|
|
||||||
body (obj/get js/document "body")
|
|
||||||
|
|
||||||
do-thing (fn do-thing []
|
|
||||||
(timers/raf
|
|
||||||
(fn []
|
|
||||||
(let [cur (.now js/performance)
|
|
||||||
ts (/ 1000 (* (- cur @last)))
|
|
||||||
val (+ @avg (* (- ts @avg) 0.1))]
|
|
||||||
|
|
||||||
(obj/set! node "innerText" val)
|
|
||||||
(vreset! last cur)
|
|
||||||
(vreset! avg val)
|
|
||||||
(do-thing)))))]
|
|
||||||
|
|
||||||
(.appendChild body node)
|
|
||||||
(do-thing)))
|
|
||||||
|
|
||||||
(defn ^:export dump-state []
|
(defn ^:export dump-state []
|
||||||
(logjs "state" @st/state)
|
(logjs "state" @st/state)
|
||||||
nil)
|
nil)
|
||||||
|
|||||||
@ -32,6 +32,7 @@ export EM_MALLOC="dlmalloc"
|
|||||||
export EMCC_CFLAGS="--no-entry \
|
export EMCC_CFLAGS="--no-entry \
|
||||||
--js-library src/js/wapi.js \
|
--js-library src/js/wapi.js \
|
||||||
-sMALLOC=$EM_MALLOC \
|
-sMALLOC=$EM_MALLOC \
|
||||||
|
-sINVOKE_RUN=0 \
|
||||||
-sALLOW_TABLE_GROWTH=0 \
|
-sALLOW_TABLE_GROWTH=0 \
|
||||||
-sALLOW_MEMORY_GROWTH=1 \
|
-sALLOW_MEMORY_GROWTH=1 \
|
||||||
-sINITIAL_HEAP=$EM_INITIAL_HEAP \
|
-sINITIAL_HEAP=$EM_INITIAL_HEAP \
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
use macros::wasm_error;
|
use macros::wasm_error;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
use crate::emscripten::init_gl;
|
||||||
|
|
||||||
use crate::mem;
|
use crate::mem;
|
||||||
use crate::render::{gpu_state::GpuState, RenderState};
|
use crate::render::{gpu_state::GpuState, RenderState};
|
||||||
use crate::state::{State, TextEditorState};
|
use crate::state::{State, TextEditorState};
|
||||||
@ -107,6 +110,7 @@ fn design_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initializes TextEditorState.
|
||||||
fn text_editor_init() {
|
fn text_editor_init() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let text_editor_state = TextEditorState::new();
|
let text_editor_state = TextEditorState::new();
|
||||||
@ -117,6 +121,8 @@ fn text_editor_init() {
|
|||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[wasm_error]
|
#[wasm_error]
|
||||||
pub extern "C" fn init(width: i32, height: i32) -> Result<()> {
|
pub extern "C" fn init(width: i32, height: i32) -> Result<()> {
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
init_gl!();
|
||||||
gpu_init();
|
gpu_init();
|
||||||
render_init(width, height);
|
render_init(width, height);
|
||||||
text_editor_init();
|
text_editor_init();
|
||||||
@ -130,7 +136,6 @@ pub extern "C" fn clean_up() -> Result<()> {
|
|||||||
// Cancel the current animation frame if it exists so
|
// Cancel the current animation frame if it exists so
|
||||||
// it won't try to render without context
|
// it won't try to render without context
|
||||||
let render_state = get_render_state();
|
let render_state = get_render_state();
|
||||||
render_state.cancel_animation_frame();
|
|
||||||
render_state.prepare_context_loss_cleanup();
|
render_state.prepare_context_loss_cleanup();
|
||||||
unsafe { DESIGN_STATE = std::ptr::null_mut() }
|
unsafe { DESIGN_STATE = std::ptr::null_mut() }
|
||||||
mem::free_bytes()?;
|
mem::free_bytes()?;
|
||||||
|
|||||||
@ -1,18 +1,4 @@
|
|||||||
addToLibrary({
|
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() {
|
wapi_notifyTilesRenderComplete: function wapi_notifyTilesRenderComplete() {
|
||||||
// The corresponding listener lives on `document` (main thread), so in a
|
// The corresponding listener lives on `document` (main thread), so in a
|
||||||
// worker context we simply skip the dispatch instead of crashing.
|
// worker context we simply skip the dispatch instead of crashing.
|
||||||
|
|||||||
@ -19,6 +19,7 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
use crate::render::{FrameType, RenderFlag};
|
||||||
|
|
||||||
use globals::{get_design_state, get_gpu_state, get_render_state};
|
use globals::{get_design_state, get_gpu_state, get_render_state};
|
||||||
|
|
||||||
@ -89,15 +90,6 @@ pub extern "C" fn set_antialias_threshold(threshold: f32) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
#[wasm_error]
|
|
||||||
pub extern "C" fn set_max_atlas_texture_size(max_px: i32) -> Result<()> {
|
|
||||||
get_render_state()
|
|
||||||
.surfaces
|
|
||||||
.set_max_atlas_texture_size(max_px);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[wasm_error]
|
#[wasm_error]
|
||||||
pub extern "C" fn set_canvas_background(raw_color: u32) -> Result<()> {
|
pub extern "C" fn set_canvas_background(raw_color: u32) -> Result<()> {
|
||||||
@ -112,7 +104,7 @@ pub extern "C" fn set_canvas_background(raw_color: u32) -> Result<()> {
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[wasm_error]
|
#[wasm_error]
|
||||||
pub extern "C" fn render(timestamp: i32) -> Result<()> {
|
pub extern "C" fn render(timestamp: i32, flags: u8) -> Result<FrameType> {
|
||||||
with_state!(state, {
|
with_state!(state, {
|
||||||
state.rebuild_touched_tiles();
|
state.rebuild_touched_tiles();
|
||||||
// Drain the throttled modifier-tile invalidation accumulated
|
// Drain the throttled modifier-tile invalidation accumulated
|
||||||
@ -128,11 +120,17 @@ pub extern "C" fn render(timestamp: i32) -> Result<()> {
|
|||||||
state.rebuild_modifier_tiles(&ids)?;
|
state.rebuild_modifier_tiles(&ids)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state
|
let frame_type = if flags & RenderFlag::Partial as u8 == RenderFlag::Partial as u8 {
|
||||||
.start_render_loop(timestamp)
|
state
|
||||||
.map_err(|_| Error::RecoverableError("Error rendering".to_string()))?;
|
.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]
|
#[no_mangle]
|
||||||
@ -179,7 +177,7 @@ pub extern "C" fn render_from_cache(_: i32) -> Result<()> {
|
|||||||
with_state!(state, {
|
with_state!(state, {
|
||||||
// Don't cancel the animation frame — let the async render
|
// Don't cancel the animation frame — let the async render
|
||||||
// continue populating the tile HashMap in the background.
|
// 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
|
// mode so it won't present stale Target content. The
|
||||||
// tile HashMap is position-independent, so tiles rendered
|
// tile HashMap is position-independent, so tiles rendered
|
||||||
// for the old viewport can be reused by the next full
|
// for the old viewport can be reused by the next full
|
||||||
@ -239,16 +237,6 @@ pub extern "C" fn render_loading_overlay() -> Result<()> {
|
|||||||
Ok(())
|
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]
|
#[no_mangle]
|
||||||
#[wasm_error]
|
#[wasm_error]
|
||||||
pub extern "C" fn reset_canvas() -> Result<()> {
|
pub extern "C" fn reset_canvas() -> Result<()> {
|
||||||
@ -301,12 +289,7 @@ pub extern "C" fn set_view_end() -> Result<()> {
|
|||||||
performance::begin_measure!("set_view_end");
|
performance::begin_measure!("set_view_end");
|
||||||
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.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();
|
||||||
@ -358,7 +341,6 @@ pub extern "C" fn set_modifiers_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.options.set_interactive_transform(false);
|
render_state.options.set_interactive_transform(false);
|
||||||
render_state.cancel_animation_frame();
|
|
||||||
performance::end_measure!("set_modifiers_end");
|
performance::end_measure!("set_modifiers_end");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -946,7 +928,9 @@ pub fn free_gpu_resources() {
|
|||||||
get_render_state().free_gpu_resources();
|
get_render_state().free_gpu_resources();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
pub fn main() {
|
||||||
#[cfg(target_arch = "wasm32")]
|
// Why an empty main?
|
||||||
init_gl!();
|
// Right now with the target `wasm32-unknown-emscripten` it is not possible
|
||||||
|
// to compile a rust project without a main and generate the necessary glue
|
||||||
|
// code. So the only option is to compile it with an empty main.
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ pub type Rect = skia::Rect;
|
|||||||
pub type Matrix = skia::Matrix;
|
pub type Matrix = skia::Matrix;
|
||||||
pub type Vector = skia::Vector;
|
pub type Vector = skia::Vector;
|
||||||
pub type Point = skia::Point;
|
pub type Point = skia::Point;
|
||||||
|
pub type Size = skia::Size;
|
||||||
|
|
||||||
const THRESHOLD: f32 = 0.001;
|
const THRESHOLD: f32 = 0.001;
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
#[allow(dead_code)]
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn get_time() -> i32 {
|
pub fn get_time() -> i32 {
|
||||||
crate::get_now!() as i32
|
crate::get_now!() as i32
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn get_time() -> i32 {
|
pub fn get_time() -> i32 {
|
||||||
let now = std::time::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
|
|||||||
@ -38,6 +38,21 @@ pub use images::*;
|
|||||||
|
|
||||||
type ClipStack = Vec<(Rect, Option<Corners>, Matrix)>;
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct NodeRenderState {
|
pub struct NodeRenderState {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
@ -334,10 +349,6 @@ pub(crate) struct RenderState {
|
|||||||
pub cached_viewbox: Viewbox,
|
pub cached_viewbox: Viewbox,
|
||||||
pub images: ImageStore,
|
pub images: ImageStore,
|
||||||
pub background_color: skia::Color,
|
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.
|
// Stack of nodes pending to be rendered.
|
||||||
pending_nodes: Vec<NodeRenderState>,
|
pending_nodes: Vec<NodeRenderState>,
|
||||||
pub current_tile: Option<tiles::Tile>,
|
pub current_tile: Option<tiles::Tile>,
|
||||||
@ -370,7 +381,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
|
||||||
@ -401,7 +412,7 @@ pub struct InteractiveDragCrop {
|
|||||||
/// Chooses a window inside the full workspace-pixel crop `[0, out_w) × [0, out_h)` with each side
|
/// Chooses a window inside the full workspace-pixel crop `[0, out_w) × [0, out_h)` with each side
|
||||||
/// at most `max_side_px` (**without scaling**): centered on the projection of
|
/// at most `max_side_px` (**without scaling**): centered on the projection of
|
||||||
/// `viewport_doc ∩ src_doc_bounds`, or on the full crop if that intersection is empty.
|
/// `viewport_doc ∩ src_doc_bounds`, or on the full crop if that intersection is empty.
|
||||||
/// `max_side_px` should match [`Surfaces::max_texture_dimension_px`] (same budget as the atlas).
|
/// `max_side_px` should match [`GpuState::max_texture_size`] (same budget as the atlas).
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn drag_crop_snapshot_window_px(
|
fn drag_crop_snapshot_window_px(
|
||||||
max_side_px: i32,
|
max_side_px: i32,
|
||||||
@ -441,22 +452,6 @@ 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 {
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
let dx = if isx.signum() != iex.signum() { 1 } else { 0 };
|
|
||||||
let dy = if isy.signum() != iey.signum() { 1 } else { 0 };
|
|
||||||
|
|
||||||
let tile_size = tiles::TILE_SIZE;
|
|
||||||
(
|
|
||||||
((iex - isx).abs() + dx) * tile_size as i32,
|
|
||||||
((iey - isy).abs() + dy) * tile_size as i32,
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderState {
|
impl RenderState {
|
||||||
/// Decide whether a top-level node can be served from `backbuffer_crop_cache` during an
|
/// Decide whether a top-level node can be served from `backbuffer_crop_cache` during an
|
||||||
/// interactive transform (drag/resize/rotate).
|
/// interactive transform (drag/resize/rotate).
|
||||||
@ -554,8 +549,6 @@ impl RenderState {
|
|||||||
cached_viewbox: Viewbox::new(0., 0.),
|
cached_viewbox: Viewbox::new(0., 0.),
|
||||||
images: ImageStore::new(),
|
images: ImageStore::new(),
|
||||||
background_color: skia::Color::TRANSPARENT,
|
background_color: skia::Color::TRANSPARENT,
|
||||||
render_request_id: None,
|
|
||||||
render_in_progress: false,
|
|
||||||
pending_nodes: vec![],
|
pending_nodes: vec![],
|
||||||
current_tile: None,
|
current_tile: None,
|
||||||
sampling_options,
|
sampling_options,
|
||||||
@ -563,9 +556,8 @@ impl RenderState {
|
|||||||
render_area_with_margins: Rect::new_empty(),
|
render_area_with_margins: Rect::new_empty(),
|
||||||
tiles,
|
tiles,
|
||||||
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![],
|
||||||
@ -787,10 +779,12 @@ impl RenderState {
|
|||||||
self.tile_viewbox
|
self.tile_viewbox
|
||||||
.set_interest(self.options.dpr_viewport_interest_area_threshold);
|
.set_interest(self.options.dpr_viewport_interest_area_threshold);
|
||||||
self.resize(
|
self.resize(
|
||||||
self.viewbox.width.floor() as i32,
|
self.viewbox.width().floor() as i32,
|
||||||
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 +830,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(())
|
||||||
}
|
}
|
||||||
@ -901,23 +895,13 @@ impl RenderState {
|
|||||||
self.flush_and_submit();
|
self.flush_and_submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
pub fn apply_render_to_final_canvas(&mut self) -> Result<()> {
|
||||||
pub fn get_canvas_at(&mut self, surface_id: SurfaceId) -> &skia::Canvas {
|
|
||||||
self.surfaces.canvas(surface_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn restore_canvas(&mut self, surface_id: SurfaceId) {
|
|
||||||
self.surfaces.canvas(surface_id).restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_render_to_final_canvas(&mut self, rect: skia::Rect) -> Result<()> {
|
|
||||||
// During interactive transforms we render tiles directly into Target; updating the cache
|
// During interactive transforms we render tiles directly into Target; updating the cache
|
||||||
// (snapshot -> atlas blit -> tiles.add) can force GPU stalls. Defer cache rebuild until
|
// (snapshot -> atlas blit -> tiles.add) can force GPU stalls. Defer cache rebuild until
|
||||||
// the interaction ends.
|
// the interaction ends.
|
||||||
if self.options.is_interactive_transform() {
|
if self.options.is_interactive_transform() {
|
||||||
let tile_rect = self.get_current_aligned_tile_bounds()?;
|
let tile_rect = self.get_current_aligned_tile_bounds()?;
|
||||||
self.surfaces.draw_current_tile_direct(
|
self.surfaces.draw_current_tile_into_backbuffer(
|
||||||
&tile_rect,
|
&tile_rect,
|
||||||
self.background_color,
|
self.background_color,
|
||||||
surfaces::DrawOnCache::No,
|
surfaces::DrawOnCache::No,
|
||||||
@ -942,7 +926,7 @@ impl RenderState {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(Error::CriticalError("Current tile not found".to_string()))?;
|
.ok_or(Error::CriticalError("Current tile not found".to_string()))?;
|
||||||
|
|
||||||
self.surfaces.cache_current_tile_texture(
|
self.surfaces.draw_current_tile_into_tile_atlas(
|
||||||
&self.tile_viewbox,
|
&self.tile_viewbox,
|
||||||
¤t_tile,
|
¤t_tile,
|
||||||
&tile_rect,
|
&tile_rect,
|
||||||
@ -950,12 +934,15 @@ impl RenderState {
|
|||||||
self.render_area,
|
self.render_area,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let rect = self.get_current_tile_bounds()?;
|
||||||
self.surfaces
|
self.surfaces
|
||||||
.draw_cached_tile_surface(current_tile, rect, self.background_color);
|
.draw_cached_tile_into_backbuffer(current_tile, &rect);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_drawing_to_render_canvas(&mut self, shape: Option<&Shape>, target: SurfaceId) {
|
/// This function draws the "surface stack" into the specified "target" surface.
|
||||||
|
pub fn draw_shape_surface_stack_into(&mut self, shape: Option<&Shape>, target: SurfaceId) {
|
||||||
performance::begin_measure!("apply_drawing_to_render_canvas");
|
performance::begin_measure!("apply_drawing_to_render_canvas");
|
||||||
|
|
||||||
let paint = skia::Paint::default();
|
let paint = skia::Paint::default();
|
||||||
@ -1663,7 +1650,7 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if apply_to_current_surface {
|
if apply_to_current_surface {
|
||||||
self.apply_drawing_to_render_canvas(Some(&shape), target_surface);
|
self.draw_shape_surface_stack_into(Some(&shape), target_surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only restore if we saved (optimization for simple shapes)
|
// Only restore if we saved (optimization for simple shapes)
|
||||||
@ -1691,14 +1678,6 @@ impl RenderState {
|
|||||||
self.surfaces.update_render_context(self.render_area, scale);
|
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) {
|
fn rebuild_backbuffer_crop_cache(&mut self, tree: ShapesPoolRef) {
|
||||||
self.backbuffer_crop_cache.clear();
|
self.backbuffer_crop_cache.clear();
|
||||||
|
|
||||||
@ -1778,12 +1757,12 @@ impl RenderState {
|
|||||||
let vb_left = self.viewbox.area.left;
|
let vb_left = self.viewbox.area.left;
|
||||||
let vb_top = self.viewbox.area.top;
|
let vb_top = self.viewbox.area.top;
|
||||||
let (bb_w, bb_h) = self.surfaces.surface_size(SurfaceId::Backbuffer);
|
let (bb_w, bb_h) = self.surfaces.surface_size(SurfaceId::Backbuffer);
|
||||||
let max_snap_px = self.surfaces.max_texture_dimension_px();
|
let max_snap_px = get_gpu_state().max_texture_size();
|
||||||
|
|
||||||
// Snapshot the atlas once for the whole pass so that all shapes sharing
|
// Snapshot the atlas once for the whole pass so that all shapes sharing
|
||||||
// the tile/atlas fallback path reuse the same GPU image rather than each
|
// the tile/atlas fallback path reuse the same GPU image rather than each
|
||||||
// triggering a separate `image_snapshot` flush.
|
// triggering a separate `image_snapshot` flush.
|
||||||
let atlas_snap = self.surfaces.atlas_snapshot_for_drag_crop();
|
let atlas_snap = self.surfaces.atlas.snapshot_for_drag_crop();
|
||||||
|
|
||||||
// Scratch surface reused across all shapes that need the tile/atlas
|
// Scratch surface reused across all shapes that need the tile/atlas
|
||||||
// fallback — avoids one WebGL texture allocation per shape.
|
// fallback — avoids one WebGL texture allocation per shape.
|
||||||
@ -1901,16 +1880,14 @@ 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,
|
||||||
// always prefer the persistent atlas. The atlas is incrementally updated as tiles finish,
|
// 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.
|
// 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.atlas.is_empty() {
|
||||||
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");
|
||||||
@ -1925,11 +1902,7 @@ impl RenderState {
|
|||||||
|
|
||||||
let interest = self.options.dpr_viewport_interest_area_threshold;
|
let interest = self.options.dpr_viewport_interest_area_threshold;
|
||||||
let TileRect(start_tile_x, start_tile_y, _, _) =
|
let TileRect(start_tile_x, start_tile_y, _, _) =
|
||||||
tiles::get_tiles_for_viewbox_with_interest(
|
tiles::get_tiles_for_viewbox_with_interest(&self.cached_viewbox, interest);
|
||||||
self.cached_viewbox,
|
|
||||||
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;
|
||||||
let translate_x = (start_tile_x as f32 * tiles::TILE_SIZE) - offset_x;
|
let translate_x = (start_tile_x as f32 * tiles::TILE_SIZE) - offset_x;
|
||||||
@ -1944,8 +1917,8 @@ impl RenderState {
|
|||||||
let cache_h = cache_dim.height as f32;
|
let cache_h = cache_dim.height as f32;
|
||||||
|
|
||||||
// Viewport in target pixels.
|
// Viewport in target pixels.
|
||||||
let vw = (self.viewbox.width * self.options.dpr).max(1.0);
|
let vw = self.viewbox.dpr_width().max(1.0);
|
||||||
let vh = (self.viewbox.height * self.options.dpr).max(1.0);
|
let vh = self.viewbox.dpr_height().max(1.0);
|
||||||
|
|
||||||
// Inverse-map viewport corners into cache coordinates.
|
// Inverse-map viewport corners into cache coordinates.
|
||||||
// target = (cache * navigate_zoom) translated by (translate_x, translate_y) (in cache coords).
|
// target = (cache * navigate_zoom) translated by (translate_x, translate_y) (in cache coords).
|
||||||
@ -1956,8 +1929,11 @@ impl RenderState {
|
|||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
|
|
||||||
let cx0 = (0.0 * inv) - translate_x;
|
// let cx0 = (0.0 * inv) - translate_x;
|
||||||
let cy0 = (0.0 * inv) - translate_y;
|
// let cy0 = (0.0 * inv) - translate_y;
|
||||||
|
// NOTA: 0.0 * inv => siempre 0
|
||||||
|
let cx0 = -translate_x;
|
||||||
|
let cy0 = -translate_y;
|
||||||
let cx1 = (vw * inv) - translate_x;
|
let cx1 = (vw * inv) - translate_x;
|
||||||
let cy1 = (vh * inv) - translate_y;
|
let cy1 = (vh * inv) - translate_y;
|
||||||
|
|
||||||
@ -1970,12 +1946,9 @@ impl RenderState {
|
|||||||
min_x >= 0.0 && min_y >= 0.0 && max_x <= cache_w && max_y <= cache_h;
|
min_x >= 0.0 && min_y >= 0.0 && max_x <= cache_w && max_y <= cache_h;
|
||||||
if !cache_covers {
|
if !cache_covers {
|
||||||
// Early return only if atlas exists; otherwise keep cache path.
|
// Early return only if atlas exists; otherwise keep cache path.
|
||||||
if self.surfaces.has_atlas() {
|
if !self.surfaces.atlas.is_empty() {
|
||||||
self.surfaces.draw_atlas_to_backbuffer(
|
self.surfaces
|
||||||
self.viewbox,
|
.draw_atlas_to_backbuffer(self.viewbox, bg_color);
|
||||||
self.options.dpr,
|
|
||||||
bg_color,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.present_frame(shapes);
|
self.present_frame(shapes);
|
||||||
performance::end_measure!("render_from_cache");
|
performance::end_measure!("render_from_cache");
|
||||||
@ -1985,21 +1958,9 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup canvas transform
|
|
||||||
{
|
|
||||||
let canvas = self.surfaces.canvas(SurfaceId::Backbuffer);
|
|
||||||
canvas.save();
|
|
||||||
canvas.scale((navigate_zoom, navigate_zoom));
|
|
||||||
canvas.translate((translate_x, translate_y));
|
|
||||||
canvas.clear(bg_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw directly from cache surface, avoiding snapshot overhead
|
// Draw directly from cache surface, avoiding snapshot overhead
|
||||||
self.surfaces.draw_cache_to_backbuffer();
|
self.surfaces.draw_cache_to_backbuffer();
|
||||||
|
|
||||||
// Restore canvas state
|
|
||||||
self.surfaces.canvas(SurfaceId::Backbuffer).restore();
|
|
||||||
|
|
||||||
// During pure pan (same zoom), draw tiles from the HashMap
|
// During pure pan (same zoom), draw tiles from the HashMap
|
||||||
// on top of the scaled Cache surface. Cached tile textures
|
// on top of the scaled Cache surface. Cached tile textures
|
||||||
// include full-quality effects (shadows, blur) from the last
|
// include full-quality effects (shadows, blur) from the last
|
||||||
@ -2008,23 +1969,14 @@ 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 rect = tile.get_rect_with_offset(&offset);
|
||||||
tx as f32 * tiles::TILE_SIZE - vb_offset_x,
|
self.surfaces.draw_cached_tile_into_backbuffer(tile, &rect);
|
||||||
ty as f32 * tiles::TILE_SIZE - vb_offset_y,
|
|
||||||
tiles::TILE_SIZE,
|
|
||||||
tiles::TILE_SIZE,
|
|
||||||
);
|
|
||||||
self.surfaces
|
|
||||||
.draw_cached_tile_surface(tile, tile_rect, bg_color);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2063,20 +2015,43 @@ impl RenderState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clears all the necessary vecs and hashmaps.
|
||||||
|
/// Also garbage collects surfaces.
|
||||||
|
fn clear(&mut self, tree: ShapesPoolRef) {
|
||||||
|
#[cfg(feature = "stats")]
|
||||||
|
self.stats.clear();
|
||||||
|
|
||||||
|
self.surfaces.gc();
|
||||||
|
|
||||||
|
self.pending_nodes.clear();
|
||||||
|
if self.pending_nodes.capacity() < tree.len() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start_render_loop(
|
pub fn start_render_loop(
|
||||||
&mut self,
|
&mut self,
|
||||||
base_object: Option<&Uuid>,
|
base_object: Option<&Uuid>,
|
||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
sync_render: bool,
|
sync_render: bool,
|
||||||
) -> Result<()> {
|
) -> Result<FrameType> {
|
||||||
#[cfg(feature = "stats")]
|
self.clear(tree);
|
||||||
self.stats.clear();
|
|
||||||
|
|
||||||
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");
|
||||||
@ -2086,7 +2061,7 @@ impl RenderState {
|
|||||||
// to clamp atlas updates. This prevents zoom-out tiles from forcing atlas
|
// to clamp atlas updates. This prevents zoom-out tiles from forcing atlas
|
||||||
// growth far beyond real content.
|
// growth far beyond real content.
|
||||||
let doc_bounds = self.compute_document_bounds(base_object, tree);
|
let doc_bounds = self.compute_document_bounds(base_object, tree);
|
||||||
self.surfaces.set_atlas_doc_bounds(doc_bounds);
|
self.surfaces.atlas.set_doc_bounds(doc_bounds);
|
||||||
|
|
||||||
self.cache_cleared_this_render = false;
|
self.cache_cleared_this_render = false;
|
||||||
if self.options.is_interactive_transform() {
|
if self.options.is_interactive_transform() {
|
||||||
@ -2109,63 +2084,44 @@ impl RenderState {
|
|||||||
| SurfaceId::Fills as u32
|
| SurfaceId::Fills as u32
|
||||||
| SurfaceId::InnerShadows as u32
|
| SurfaceId::InnerShadows as u32
|
||||||
| SurfaceId::TextDropShadows as u32;
|
| SurfaceId::TextDropShadows as u32;
|
||||||
|
|
||||||
self.surfaces.apply_mut(surface_ids, |s| {
|
self.surfaces.apply_mut(surface_ids, |s| {
|
||||||
s.canvas().scale((scale, scale));
|
s.canvas().scale((scale, scale));
|
||||||
});
|
});
|
||||||
|
|
||||||
let viewbox_cache_size = get_cache_size(
|
self.surfaces.resize_cache_from_viewbox(
|
||||||
self.viewbox,
|
&self.viewbox,
|
||||||
scale,
|
&self.cached_viewbox,
|
||||||
self.options.dpr_viewport_interest_area_threshold,
|
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
|
|
||||||
// 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
|
|
||||||
{
|
|
||||||
self.surfaces.resize_cache(
|
|
||||||
viewbox_cache_size,
|
|
||||||
self.options.dpr_viewport_interest_area_threshold,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME - review debug
|
// FIXME - review debug
|
||||||
// debug::render_debug_tiles_for_viewbox(self);
|
// debug::render_debug_tiles_for_viewbox(self);
|
||||||
|
|
||||||
let _tile_start = performance::begin_timed_log!("tile_cache_update");
|
let _tile_start = performance::begin_timed_log!("tile_cache_update");
|
||||||
|
|
||||||
performance::begin_measure!("tile_cache");
|
performance::begin_measure!("tile_cache");
|
||||||
let only_visible = self.options.is_interactive_transform();
|
let only_visible = self.options.is_interactive_transform();
|
||||||
self.pending_tiles
|
self.pending_tiles
|
||||||
.update(&self.tile_viewbox, &self.surfaces, only_visible);
|
.update(&self.tile_viewbox, &self.surfaces, only_visible);
|
||||||
performance::end_measure!("tile_cache");
|
performance::end_measure!("tile_cache");
|
||||||
|
|
||||||
performance::end_timed_log!("tile_cache_update", _tile_start);
|
performance::end_timed_log!("tile_cache_update", _tile_start);
|
||||||
|
|
||||||
self.pending_nodes.clear();
|
self.draw_shape_surface_stack_into(None, SurfaceId::Current);
|
||||||
if self.pending_nodes.capacity() < tree.len() {
|
|
||||||
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;
|
|
||||||
|
|
||||||
self.render_in_progress = true;
|
|
||||||
|
|
||||||
self.apply_drawing_to_render_canvas(None, SurfaceId::Current);
|
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
let mut frame_type = FrameType::None;
|
||||||
if sync_render {
|
if sync_render {
|
||||||
self.render_shape_tree_sync(base_object, tree, timestamp)?;
|
frame_type = self.render_shape_tree_sync(base_object, tree, timestamp)?;
|
||||||
} else {
|
} 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 {
|
||||||
|
self.options.capture_frames -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Update cached_viewbox after visible tiles render
|
// Update cached_viewbox after visible tiles render
|
||||||
// synchronously so that render_from_cache uses the correct
|
// synchronously so that render_from_cache uses the correct
|
||||||
// zoom ratio even if interest-area tiles are still rendering
|
// zoom ratio even if interest-area tiles are still rendering
|
||||||
@ -2180,7 +2136,7 @@ impl RenderState {
|
|||||||
|
|
||||||
performance::end_measure!("start_render_loop");
|
performance::end_measure!("start_render_loop");
|
||||||
performance::end_timed_log!("start_render_loop", _start);
|
performance::end_timed_log!("start_render_loop", _start);
|
||||||
Ok(())
|
Ok(frame_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_document_bounds(
|
fn compute_document_bounds(
|
||||||
@ -2214,37 +2170,45 @@ impl RenderState {
|
|||||||
acc
|
acc
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_animation_frame(
|
pub fn continue_render_loop(
|
||||||
&mut self,
|
&mut self,
|
||||||
base_object: Option<&Uuid>,
|
base_object: Option<&Uuid>,
|
||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
) -> Result<()> {
|
) -> Result<FrameType> {
|
||||||
performance::begin_measure!("process_animation_frame");
|
performance::begin_measure!("continue_render_loop");
|
||||||
self.render_shape_tree_partial(base_object, tree, timestamp, true)?;
|
let frame_type = self.render_shape_tree_partial(base_object, tree, timestamp, true)?;
|
||||||
|
|
||||||
if self.render_in_progress {
|
if !self.options.is_interactive_transform() {
|
||||||
// Partial frame: just flush GPU work. The display shows the last
|
self.surfaces
|
||||||
// fully submitted frame; no need to copy or draw UI overlays here.
|
.draw_tile_atlas_to_backbuffer(&self.viewbox, &self.tile_viewbox);
|
||||||
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);
|
|
||||||
}
|
|
||||||
// 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");
|
match frame_type {
|
||||||
Ok(())
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
performance::end_measure!("continue_render_loop");
|
||||||
|
Ok(frame_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_shape_tree_sync(
|
pub fn render_shape_tree_sync(
|
||||||
@ -2252,10 +2216,10 @@ impl RenderState {
|
|||||||
base_object: Option<&Uuid>,
|
base_object: Option<&Uuid>,
|
||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
) -> Result<()> {
|
) -> Result<FrameType> {
|
||||||
self.render_shape_tree_partial(base_object, tree, timestamp, false)?;
|
self.render_shape_tree_partial(base_object, tree, timestamp, false)?;
|
||||||
self.present_frame(tree);
|
self.present_frame(tree);
|
||||||
Ok(())
|
Ok(FrameType::Full)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_shape_pixels(
|
pub fn render_shape_pixels(
|
||||||
@ -2560,18 +2524,11 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_tile_bounds(&mut self) -> Result<Rect> {
|
pub fn get_current_tile_bounds(&mut self) -> Result<Rect> {
|
||||||
let tiles::Tile(tile_x, tile_y) = self
|
let tile = self
|
||||||
.current_tile
|
.current_tile
|
||||||
.ok_or(Error::CriticalError("Current tile not found".to_string()))?;
|
.ok_or(Error::CriticalError("Current tile not found".to_string()))?;
|
||||||
let scale = self.get_scale();
|
let offset = self.viewbox.get_offset();
|
||||||
let offset_x = self.viewbox.area.left * scale;
|
Ok(tile.get_rect_with_offset(&offset))
|
||||||
let offset_y = self.viewbox.area.top * scale;
|
|
||||||
Ok(Rect::from_xywh(
|
|
||||||
(tile_x as f32 * tiles::TILE_SIZE) - offset_x,
|
|
||||||
(tile_y as f32 * tiles::TILE_SIZE) - offset_y,
|
|
||||||
tiles::TILE_SIZE,
|
|
||||||
tiles::TILE_SIZE,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_rect_bounds(&mut self, rect: skia::Rect) -> Rect {
|
pub fn get_rect_bounds(&mut self, rect: skia::Rect) -> Rect {
|
||||||
@ -3337,7 +3294,7 @@ impl RenderState {
|
|||||||
.canvas(SurfaceId::DropShadows)
|
.canvas(SurfaceId::DropShadows)
|
||||||
.clear(skia::Color::TRANSPARENT);
|
.clear(skia::Color::TRANSPARENT);
|
||||||
} else if visited_children {
|
} else if visited_children {
|
||||||
self.apply_drawing_to_render_canvas(Some(element), target_surface);
|
self.draw_shape_surface_stack_into(Some(element), target_surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip nested state updates for flattened containers
|
// Skip nested state updates for flattened containers
|
||||||
@ -3406,6 +3363,7 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
iteration += 1;
|
iteration += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((is_empty, false))
|
Ok((is_empty, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3415,7 +3373,7 @@ impl RenderState {
|
|||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
allow_stop: bool,
|
allow_stop: bool,
|
||||||
) -> Result<()> {
|
) -> Result<FrameType> {
|
||||||
let mut should_stop = false;
|
let mut should_stop = false;
|
||||||
let root_ids = {
|
let root_ids = {
|
||||||
if let Some(shape_id) = base_object {
|
if let Some(shape_id) = base_object {
|
||||||
@ -3430,53 +3388,20 @@ 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) {
|
// NOTE: For now we don't need to cover the case where the tile
|
||||||
performance::begin_measure!("render_shape_tree::cached");
|
// is not cached because everything will be handled from 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)?;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
if self.options.capture_frames > 0 {
|
||||||
|
debug::console_debug_surface(self, SurfaceId::Backbuffer);
|
||||||
|
}
|
||||||
|
|
||||||
if early_return {
|
if early_return {
|
||||||
return Ok(());
|
return Ok(FrameType::Partial);
|
||||||
}
|
}
|
||||||
performance::end_measure!("render_shape_tree::uncached");
|
performance::end_measure!("render_shape_tree::uncached");
|
||||||
|
|
||||||
@ -3489,13 +3414,13 @@ impl RenderState {
|
|||||||
if self.options.is_interactive_transform() {
|
if self.options.is_interactive_transform() {
|
||||||
// During drag, avoid snapshot-based caching. Draw Current directly
|
// During drag, avoid snapshot-based caching. Draw Current directly
|
||||||
// into Target (and Cache) to reduce stalls.
|
// into Target (and Cache) to reduce stalls.
|
||||||
self.surfaces.draw_current_tile_direct(
|
self.surfaces.draw_current_tile_into_backbuffer(
|
||||||
&tile_rect,
|
&tile_rect,
|
||||||
self.background_color,
|
self.background_color,
|
||||||
surfaces::DrawOnCache::Yes,
|
surfaces::DrawOnCache::Yes,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
self.apply_render_to_final_canvas(tile_rect)?;
|
self.apply_render_to_final_canvas()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.options.is_debug_visible() {
|
if self.options.is_debug_visible() {
|
||||||
@ -3506,10 +3431,9 @@ impl RenderState {
|
|||||||
tile_rect,
|
tile_rect,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3527,62 +3451,65 @@ impl RenderState {
|
|||||||
// empty tile.
|
// empty tile.
|
||||||
self.current_tile_had_shapes = false;
|
self.current_tile_had_shapes = false;
|
||||||
|
|
||||||
if !self.surfaces.has_cached_tile_surface(next_tile) {
|
let Some(ids) = self.tiles.get_shapes_at(next_tile) else {
|
||||||
if let Some(ids) = self.tiles.get_shapes_at(next_tile) {
|
// If the tile is empty we do not need to render it.
|
||||||
// Check if any shape on this tile has a background blur.
|
continue;
|
||||||
// If so, we need ALL root shapes rendered (not just those
|
};
|
||||||
// assigned to this tile) because the blur snapshots Current
|
|
||||||
// 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
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// We only need first level shapes, in the same order as the parent node.
|
if self.surfaces.has_cached_tile_surface(next_tile) {
|
||||||
//
|
// If the tile is cached, then we do not need to
|
||||||
// During interactive transforms we may invalidate only the modified shapes
|
// render it.
|
||||||
// (to avoid massive ancestor eviction). However, we still composite full
|
continue;
|
||||||
// tiles (we clear the tile rect before drawing Current), so we must render
|
}
|
||||||
// all root shapes that can contribute to this tile; otherwise, unchanged
|
|
||||||
// siblings inside the same tile would disappear.
|
// Check if any shape on this tile has a background blur.
|
||||||
let mut valid_ids = Vec::with_capacity(ids.len());
|
// If so, we need ALL root shapes rendered (not just those
|
||||||
if self.options.is_interactive_transform() || tile_has_bg_blur {
|
// assigned to this tile) because the blur snapshots Current
|
||||||
valid_ids.extend(root_ids.iter().copied());
|
// which must contain the shapes behind it.
|
||||||
} else {
|
let tile_has_bg_blur = ids.iter().any(|id| {
|
||||||
for root_id in root_ids.iter() {
|
tree.get(id).is_some_and(|s| {
|
||||||
if ids.contains(root_id) {
|
s.blur
|
||||||
valid_ids.push(*root_id);
|
.is_some_and(|b| !b.hidden && b.blur_type == BlurType::BackgroundBlur)
|
||||||
}
|
})
|
||||||
}
|
});
|
||||||
|
|
||||||
|
// We only need first level shapes, in the same order as the parent node.
|
||||||
|
//
|
||||||
|
// During interactive transforms we may invalidate only the modified shapes
|
||||||
|
// (to avoid massive ancestor eviction). However, we still composite full
|
||||||
|
// tiles (we clear the tile rect before drawing Current), so we must render
|
||||||
|
// all root shapes that can contribute to this tile; otherwise, unchanged
|
||||||
|
// siblings inside the same tile would disappear.
|
||||||
|
let mut valid_ids = Vec::with_capacity(ids.len());
|
||||||
|
if self.options.is_interactive_transform() || tile_has_bg_blur {
|
||||||
|
valid_ids.extend(root_ids.iter().copied());
|
||||||
|
} else {
|
||||||
|
for root_id in root_ids.iter() {
|
||||||
|
if ids.contains(root_id) {
|
||||||
|
valid_ids.push(*root_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !valid_ids.is_empty() {
|
|
||||||
self.current_tile_had_shapes = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !valid_ids.is_empty() {
|
||||||
|
self.current_tile_had_shapes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
|
// If there are no more pending tiles, stop.
|
||||||
should_stop = true;
|
should_stop = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.render_in_progress = false;
|
|
||||||
self.surfaces.gc();
|
|
||||||
|
|
||||||
// Mark cache as valid for render_from_cache.
|
// Mark cache as valid for render_from_cache.
|
||||||
// Only update for full-quality renders (non-fast mode).
|
// Only update for full-quality renders (non-fast mode).
|
||||||
// An async render can complete while fast mode is active
|
// An async render can complete while fast mode is active
|
||||||
@ -3595,7 +3522,7 @@ impl RenderState {
|
|||||||
self.cached_viewbox = self.viewbox;
|
self.cached_viewbox = self.viewbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(FrameType::Full)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -3648,7 +3575,7 @@ impl RenderState {
|
|||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
) -> HashSet<tiles::Tile> {
|
) -> HashSet<tiles::Tile> {
|
||||||
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree);
|
let tile_rect = self.get_tiles_for_shape(shape, tree);
|
||||||
|
|
||||||
// Collect old tiles to avoid borrow conflict with remove_shape_at
|
// Collect old tiles to avoid borrow conflict with remove_shape_at
|
||||||
let old_tiles: Vec<_> = self
|
let old_tiles: Vec<_> = self
|
||||||
@ -3661,7 +3588,7 @@ impl RenderState {
|
|||||||
// When the shape has an active modifier (i.e. is being moved/resized),
|
// When the shape has an active modifier (i.e. is being moved/resized),
|
||||||
// clear its OLD doc-space extent from the atlas using the raw
|
// clear its OLD doc-space extent from the atlas using the raw
|
||||||
// (pre-modifier) shape. The per-tile clearing done later via
|
// (pre-modifier) shape. The per-tile clearing done later via
|
||||||
// `clear_tile_in_atlas` only covers tiles tracked in `atlas_tile_doc_rects`
|
// `clear_tile_in_atlas` only covers tiles tracked in `atlas.tile_doc_rects`
|
||||||
// at the current zoom level. However, the atlas may also contain stale
|
// at the current zoom level. However, the atlas may also contain stale
|
||||||
// pixels from previous zoom levels (tiles are larger / smaller in doc
|
// pixels from previous zoom levels (tiles are larger / smaller in doc
|
||||||
// space at different zoom scales) that were never re-tracked after a zoom
|
// space at different zoom scales) that were never re-tracked after a zoom
|
||||||
@ -3673,7 +3600,9 @@ impl RenderState {
|
|||||||
if tree.get_modifier(&shape.id).is_some() {
|
if tree.get_modifier(&shape.id).is_some() {
|
||||||
if let Some(raw_shape) = tree.get_raw(&shape.id) {
|
if let Some(raw_shape) = tree.get_raw(&shape.id) {
|
||||||
let old_extrect = raw_shape.extrect(tree, 1.0);
|
let old_extrect = raw_shape.extrect(tree, 1.0);
|
||||||
self.surfaces.clear_doc_rect_in_atlas_clipped(old_extrect);
|
self.surfaces
|
||||||
|
.atlas
|
||||||
|
.clear_doc_rect_in_atlas_clipped(old_extrect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3684,7 +3613,7 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Then, add the shape to the new tiles
|
// Then, add the shape to the new tiles
|
||||||
for tile in (rsx..=rex).flat_map(|x| (rsy..=rey).map(move |y| tiles::Tile::from(x, y))) {
|
for tile in tile_rect.iter(true) {
|
||||||
self.tiles.add_shape_at(tile, shape.id);
|
self.tiles.add_shape_at(tile, shape.id);
|
||||||
result.insert(tile);
|
result.insert(tile);
|
||||||
}
|
}
|
||||||
@ -3713,16 +3642,13 @@ impl RenderState {
|
|||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
) -> Vec<tiles::Tile> {
|
) -> Vec<tiles::Tile> {
|
||||||
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree);
|
let tile_rect = self.get_tiles_for_shape(shape, tree);
|
||||||
|
|
||||||
let old_tiles: HashSet<tiles::Tile> = self
|
let old_tiles: HashSet<tiles::Tile> = self
|
||||||
.tiles
|
.tiles
|
||||||
.get_tiles_of(shape.id)
|
.get_tiles_of(shape.id)
|
||||||
.map_or(HashSet::new(), |tiles| tiles.iter().copied().collect());
|
.map_or(HashSet::new(), |tiles| tiles.iter().copied().collect());
|
||||||
|
|
||||||
let new_tiles: HashSet<tiles::Tile> = (rsx..=rex)
|
let new_tiles: HashSet<tiles::Tile> = tile_rect.iter(true).collect();
|
||||||
.flat_map(|x| (rsy..=rey).map(move |y| tiles::Tile::from(x, y)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Tiles where shape is being removed from index (left interest area)
|
// Tiles where shape is being removed from index (left interest area)
|
||||||
let removed: Vec<_> = old_tiles.difference(&new_tiles).copied().collect();
|
let removed: Vec<_> = old_tiles.difference(&new_tiles).copied().collect();
|
||||||
@ -3747,18 +3673,16 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add the tiles forthe shape to the index.
|
* Add the tiles for the shape to the index.
|
||||||
* returns the tiles that have been updated
|
* returns the tiles that have been updated
|
||||||
*/
|
*/
|
||||||
pub fn add_shape_tiles(&mut self, shape: &Shape, tree: ShapesPoolRef) -> Vec<tiles::Tile> {
|
pub fn add_shape_tiles(&mut self, shape: &Shape, tree: ShapesPoolRef) -> Vec<tiles::Tile> {
|
||||||
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree);
|
performance::begin_measure!("add_shape_tiles");
|
||||||
let tiles: Vec<_> = (rsx..=rex)
|
let tiles: Vec<tiles::Tile> = self.get_tiles_for_shape(shape, tree).iter(true).collect();
|
||||||
.flat_map(|x| (rsy..=rey).map(move |y| tiles::Tile::from(x, y)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for tile in tiles.iter() {
|
for tile in tiles.iter() {
|
||||||
self.tiles.add_shape_at(*tile, shape.id);
|
self.tiles.add_shape_at(*tile, shape.id);
|
||||||
}
|
}
|
||||||
|
performance::end_measure!("add_shape_tiles");
|
||||||
tiles
|
tiles
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3771,8 +3695,9 @@ impl RenderState {
|
|||||||
/// survive so that fast-mode renders during pan still show shadows/blur.
|
/// survive so that fast-mode renders during pan still show shadows/blur.
|
||||||
pub fn rebuild_tile_index(&mut self, tree: ShapesPoolRef) {
|
pub fn rebuild_tile_index(&mut self, tree: ShapesPoolRef) {
|
||||||
let zoom_changed = self.zoom_changed();
|
let zoom_changed = self.zoom_changed();
|
||||||
|
performance::begin_measure!("rebuild_tile_index");
|
||||||
let mut nodes = vec![Uuid::nil()];
|
let mut nodes = Vec::<Uuid>::with_capacity(64);
|
||||||
|
nodes.push(Uuid::nil());
|
||||||
while let Some(shape_id) = nodes.pop() {
|
while let Some(shape_id) = nodes.pop() {
|
||||||
if let Some(shape) = tree.get(&shape_id) {
|
if let Some(shape) = tree.get(&shape_id) {
|
||||||
if shape_id != Uuid::nil() {
|
if shape_id != Uuid::nil() {
|
||||||
@ -3789,6 +3714,7 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
performance::end_measure!("rebuild_tile_index");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rebuild_tiles_shallow(&mut self, tree: ShapesPoolRef) {
|
pub fn rebuild_tiles_shallow(&mut self, tree: ShapesPoolRef) {
|
||||||
@ -3927,11 +3853,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 {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
use super::{tiles, RenderState, SurfaceId};
|
use super::{tiles, RenderState, SurfaceId};
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
@ -22,7 +23,6 @@ fn get_debug_rect(rect: Rect) -> Rect {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn render_debug_view(render_state: &mut RenderState) {
|
fn render_debug_view(render_state: &mut RenderState) {
|
||||||
let mut paint = skia::Paint::default();
|
let mut paint = skia::Paint::default();
|
||||||
paint.set_style(skia::PaintStyle::Stroke);
|
paint.set_style(skia::PaintStyle::Stroke);
|
||||||
@ -36,7 +36,6 @@ fn render_debug_view(render_state: &mut RenderState) {
|
|||||||
.draw_rect(rect, &paint);
|
.draw_rect(rect, &paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn render_debug_cache_surface(render_state: &mut RenderState) {
|
pub fn render_debug_cache_surface(render_state: &mut RenderState) {
|
||||||
let canvas = render_state.surfaces.canvas(SurfaceId::Debug);
|
let canvas = render_state.surfaces.canvas(SurfaceId::Debug);
|
||||||
canvas.save();
|
canvas.save();
|
||||||
@ -78,7 +77,6 @@ pub fn render_wasm_label(render_state: &mut RenderState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn render_debug_tiles_for_viewbox(render_state: &mut RenderState) {
|
pub fn render_debug_tiles_for_viewbox(render_state: &mut RenderState) {
|
||||||
let tiles::TileRect(sx, sy, ex, ey) = render_state.tile_viewbox.interest_rect;
|
let tiles::TileRect(sx, sy, ex, ey) = render_state.tile_viewbox.interest_rect;
|
||||||
let canvas = render_state.surfaces.canvas(SurfaceId::Debug);
|
let canvas = render_state.surfaces.canvas(SurfaceId::Debug);
|
||||||
@ -91,7 +89,6 @@ pub fn render_debug_tiles_for_viewbox(render_state: &mut RenderState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Renders the tiles in the viewbox
|
// Renders the tiles in the viewbox
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn render_debug_viewbox_tiles(render_state: &mut RenderState) {
|
pub fn render_debug_viewbox_tiles(render_state: &mut RenderState) {
|
||||||
let scale = render_state.get_scale();
|
let scale = render_state.get_scale();
|
||||||
let canvas = render_state.surfaces.canvas(SurfaceId::Debug);
|
let canvas = render_state.surfaces.canvas(SurfaceId::Debug);
|
||||||
@ -101,30 +98,25 @@ pub fn render_debug_viewbox_tiles(render_state: &mut RenderState) {
|
|||||||
paint.set_stroke_width(1.);
|
paint.set_stroke_width(1.);
|
||||||
|
|
||||||
let tile_size = tiles::get_tile_size(scale);
|
let tile_size = tiles::get_tile_size(scale);
|
||||||
let tiles::TileRect(sx, sy, ex, ey) =
|
let tile_rect = tiles::get_tiles_for_rect(render_state.viewbox.area, tile_size);
|
||||||
tiles::get_tiles_for_rect(render_state.viewbox.area, tile_size);
|
let tiles::TileRect(sx, sy, ex, ey) = tile_rect;
|
||||||
|
|
||||||
let str_rect = format!("{} {} {} {}", sx, sy, ex, ey);
|
let str_rect = format!("{} {} {} {}", sx, sy, ex, ey);
|
||||||
|
|
||||||
let debug_font = render_state.fonts.debug_font();
|
let debug_font = render_state.fonts.debug_font();
|
||||||
canvas.draw_str(str_rect, skia::Point::new(100.0, 100.0), debug_font, &paint);
|
canvas.draw_str(str_rect, skia::Point::new(100.0, 100.0), debug_font, &paint);
|
||||||
|
|
||||||
let tile_size = tiles::get_tile_size(scale);
|
let tile_size = tiles::get_tile_size(scale);
|
||||||
for y in sy..=ey {
|
for tile in tile_rect.iter(true) {
|
||||||
for x in sx..=ex {
|
let tiles::Tile(x, y) = tile;
|
||||||
let rect = Rect::from_xywh(
|
let rect = tile.get_rect_with_size(tile_size);
|
||||||
x as f32 * tile_size,
|
let debug_rect = get_debug_rect(rect);
|
||||||
y as f32 * tile_size,
|
let p = skia::Point::new(debug_rect.x(), debug_rect.y() - 1.);
|
||||||
tile_size,
|
let str = format!("{}:{}", x, y);
|
||||||
tile_size,
|
let debug_font = render_state.fonts.debug_font();
|
||||||
);
|
paint.set_style(skia::PaintStyle::Fill);
|
||||||
let debug_rect = get_debug_rect(rect);
|
canvas.draw_str(str, p, debug_font, &paint);
|
||||||
let p = skia::Point::new(debug_rect.x(), debug_rect.y() - 1.);
|
canvas.draw_rect(debug_rect, &paint);
|
||||||
let str = format!("{}:{}", x, y);
|
|
||||||
let debug_font = render_state.fonts.debug_font();
|
|
||||||
paint.set_style(skia::PaintStyle::Fill);
|
|
||||||
canvas.draw_str(str, p, debug_font, &paint);
|
|
||||||
canvas.draw_rect(debug_rect, &paint);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,13 +179,11 @@ pub fn render_debug_shape(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn trap() {
|
pub fn trap() {
|
||||||
run_script!("debugger");
|
run_script!("debugger");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum SurfaceBackendKind {
|
pub enum SurfaceBackendKind {
|
||||||
@ -203,7 +193,6 @@ pub enum SurfaceBackendKind {
|
|||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn classify_surface_backend(surface: &mut skia::Surface) -> SurfaceBackendKind {
|
pub fn classify_surface_backend(surface: &mut skia::Surface) -> SurfaceBackendKind {
|
||||||
if skia::gpu::surfaces::get_backend_texture(
|
if skia::gpu::surfaces::get_backend_texture(
|
||||||
@ -231,7 +220,6 @@ pub fn classify_surface_backend(surface: &mut skia::Surface) -> SurfaceBackendKi
|
|||||||
SurfaceBackendKind::Unknown
|
SurfaceBackendKind::Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn console_debug_surface(render_state: &mut RenderState, id: SurfaceId) {
|
pub fn console_debug_surface(render_state: &mut RenderState, id: SurfaceId) {
|
||||||
let base64_image = render_state
|
let base64_image = render_state
|
||||||
@ -242,7 +230,6 @@ pub fn console_debug_surface(render_state: &mut RenderState, id: SurfaceId) {
|
|||||||
run_script!(format!("console.log('%c ', 'font-size: 1px; background: url(data:image/png;base64,{base64_image}) no-repeat; padding: 100px; background-size: contain;')"));
|
run_script!(format!("console.log('%c ', 'font-size: 1px; background: url(data:image/png;base64,{base64_image}) no-repeat; padding: 100px; background-size: contain;')"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn console_debug_surface_base64(render_state: &mut RenderState, id: SurfaceId) {
|
pub fn console_debug_surface_base64(render_state: &mut RenderState, id: SurfaceId) {
|
||||||
let base64_image = render_state
|
let base64_image = render_state
|
||||||
@ -253,7 +240,6 @@ pub fn console_debug_surface_base64(render_state: &mut RenderState, id: SurfaceI
|
|||||||
println!("{}", base64_image);
|
println!("{}", base64_image);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn console_debug_surface_rect(render_state: &mut RenderState, id: SurfaceId, rect: skia::Rect) {
|
pub fn console_debug_surface_rect(render_state: &mut RenderState, id: SurfaceId, rect: skia::Rect) {
|
||||||
let int_rect = skia::IRect::from_ltrb(
|
let int_rect = skia::IRect::from_ltrb(
|
||||||
@ -273,6 +259,16 @@ pub fn console_debug_surface_rect(render_state: &mut RenderState, id: SurfaceId,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
#[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);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[wasm_error]
|
#[wasm_error]
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
|||||||
@ -5,6 +5,9 @@ use skia_safe::gpu::{
|
|||||||
};
|
};
|
||||||
use skia_safe::{self as skia, ISize};
|
use skia_safe::{self as skia, ISize};
|
||||||
|
|
||||||
|
const MIN_MAX_TEXTURE_SIZE: i32 = 512;
|
||||||
|
const MAX_MAX_TEXTURE_SIZE: i32 = 8192 * 2;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GpuState {
|
pub struct GpuState {
|
||||||
pub context: DirectContext,
|
pub context: DirectContext,
|
||||||
@ -28,6 +31,7 @@ impl GpuState {
|
|||||||
let context = gpu::direct_contexts::make_gl(interface, Some(&context_options)).ok_or(
|
let context = gpu::direct_contexts::make_gl(interface, Some(&context_options)).ok_or(
|
||||||
Error::CriticalError("Failed to create GL context".to_string()),
|
Error::CriticalError("Failed to create GL context".to_string()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let framebuffer_info = {
|
let framebuffer_info = {
|
||||||
let mut fboid: gl::types::GLint = 0;
|
let mut fboid: gl::types::GLint = 0;
|
||||||
unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
|
unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
|
||||||
@ -47,6 +51,12 @@ impl GpuState {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn max_texture_size(&self) -> i32 {
|
||||||
|
self.context
|
||||||
|
.max_texture_size()
|
||||||
|
.clamp(MIN_MAX_TEXTURE_SIZE, MAX_MAX_TEXTURE_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
fn delete_gl_texture(&mut self, texture_id: gl::types::GLuint) -> bool {
|
fn delete_gl_texture(&mut self, texture_id: gl::types::GLuint) -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
gl::DeleteTextures(1, &texture_id);
|
gl::DeleteTextures(1, &texture_id);
|
||||||
|
|||||||
@ -30,6 +30,7 @@ pub struct RenderOptions {
|
|||||||
pub max_blocking_time_ms: i32,
|
pub max_blocking_time_ms: i32,
|
||||||
pub node_batch_threshold: i32,
|
pub node_batch_threshold: i32,
|
||||||
pub blur_downscale_threshold: f32,
|
pub blur_downscale_threshold: f32,
|
||||||
|
pub capture_frames: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RenderOptions {
|
impl Default for RenderOptions {
|
||||||
@ -45,6 +46,7 @@ impl Default for RenderOptions {
|
|||||||
max_blocking_time_ms: MAX_BLOCKING_TIME_MS,
|
max_blocking_time_ms: MAX_BLOCKING_TIME_MS,
|
||||||
node_batch_threshold: NODE_BATCH_THRESHOLD,
|
node_batch_threshold: NODE_BATCH_THRESHOLD,
|
||||||
blur_downscale_threshold: BLUR_DOWNSCALE_THRESHOLD,
|
blur_downscale_threshold: BLUR_DOWNSCALE_THRESHOLD,
|
||||||
|
capture_frames: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,6 +69,10 @@ impl RenderOptions {
|
|||||||
self.fast_mode = enabled;
|
self.fast_mode = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_capture_frames(&mut self, capture_frames: i32) {
|
||||||
|
self.capture_frames = capture_frames;
|
||||||
|
}
|
||||||
|
|
||||||
/// Updates the dpr viewport interest area threshold.
|
/// Updates the dpr viewport interest area threshold.
|
||||||
/// This function is updated when the dpr or the
|
/// This function is updated when the dpr or the
|
||||||
/// viewport_interest_area_threshold is changed
|
/// viewport_interest_area_threshold is changed
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -3,8 +3,8 @@ use crate::{
|
|||||||
error::Result,
|
error::Result,
|
||||||
math::Rect,
|
math::Rect,
|
||||||
shapes::{
|
shapes::{
|
||||||
calculate_position_data, calculate_text_layout_data, set_paint_fill, ParagraphBuilderGroup,
|
calculate_text_layout_data, set_paint_fill, ParagraphBuilderGroup, Stroke, StrokeKind,
|
||||||
Stroke, StrokeKind, TextContent,
|
TextContent,
|
||||||
},
|
},
|
||||||
utils::{get_fallback_fonts, get_font_collection},
|
utils::{get_fallback_fonts, get_font_collection},
|
||||||
};
|
};
|
||||||
@ -12,7 +12,7 @@ use skia_safe::{
|
|||||||
self as skia,
|
self as skia,
|
||||||
canvas::SaveLayerRec,
|
canvas::SaveLayerRec,
|
||||||
textlayout::{ParagraphBuilder, StyleMetrics, TextDecoration, TextStyle},
|
textlayout::{ParagraphBuilder, StyleMetrics, TextDecoration, TextStyle},
|
||||||
Canvas, ImageFilter, Paint, Path,
|
Canvas, ImageFilter, Paint,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn stroke_paragraph_builder_group_from_text(
|
pub fn stroke_paragraph_builder_group_from_text(
|
||||||
@ -552,78 +552,6 @@ pub fn calculate_decoration_metrics(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn calculate_total_paragraphs_height(paragraphs: &mut [ParagraphBuilder], width: f32) -> f32 {
|
|
||||||
paragraphs
|
|
||||||
.iter_mut()
|
|
||||||
.map(|p| {
|
|
||||||
let mut paragraph = p.build();
|
|
||||||
paragraph.layout(width);
|
|
||||||
paragraph.height()
|
|
||||||
})
|
|
||||||
.sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn calculate_all_paragraphs_height(
|
|
||||||
paragraph_groups: &mut [Vec<ParagraphBuilder>],
|
|
||||||
width: f32,
|
|
||||||
) -> f32 {
|
|
||||||
paragraph_groups
|
|
||||||
.iter_mut()
|
|
||||||
.map(|group| {
|
|
||||||
// For stroke groups, only count the first paragraph to avoid double-counting
|
|
||||||
if group.len() > 1 {
|
|
||||||
let mut paragraph = group[0].build();
|
|
||||||
paragraph.layout(width);
|
|
||||||
paragraph.height()
|
|
||||||
} else {
|
|
||||||
calculate_total_paragraphs_height(group, width)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render text paths (unused)
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn render_as_path(
|
|
||||||
render_state: &mut RenderState,
|
|
||||||
paths: &Vec<(Path, Paint)>,
|
|
||||||
surface_id: Option<SurfaceId>,
|
|
||||||
) {
|
|
||||||
let canvas = render_state
|
|
||||||
.surfaces
|
|
||||||
.canvas_and_mark_dirty(surface_id.unwrap_or(SurfaceId::Fills));
|
|
||||||
|
|
||||||
for (path, paint) in paths {
|
|
||||||
// Note: path can be empty
|
|
||||||
canvas.draw_path(path, paint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn render_position_data(
|
|
||||||
render_state: &mut RenderState,
|
|
||||||
surface_id: SurfaceId,
|
|
||||||
shape: &Shape,
|
|
||||||
text_content: &TextContent,
|
|
||||||
) {
|
|
||||||
let position_data = calculate_position_data(shape, text_content, false);
|
|
||||||
|
|
||||||
let mut paint = skia::Paint::default();
|
|
||||||
paint.set_style(skia::PaintStyle::Stroke);
|
|
||||||
paint.set_color(skia::Color::from_argb(255, 255, 0, 0));
|
|
||||||
paint.set_stroke_width(2.);
|
|
||||||
|
|
||||||
for pd in position_data {
|
|
||||||
let rect = Rect::from_xywh(pd.x, pd.y, pd.width, pd.height);
|
|
||||||
render_state
|
|
||||||
.surfaces
|
|
||||||
.canvas_and_mark_dirty(surface_id)
|
|
||||||
.draw_rect(rect, &paint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// How to use it?
|
// How to use it?
|
||||||
// Type::Text(text_content) => {
|
// Type::Text(text_content) => {
|
||||||
// self.surfaces
|
// self.surfaces
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum Layout {
|
pub enum Layout {
|
||||||
FlexLayout(LayoutData, FlexData),
|
FlexLayout(LayoutData, FlexData),
|
||||||
GridLayout(LayoutData, GridData),
|
GridLayout(LayoutData, GridData),
|
||||||
|
|||||||
@ -501,7 +501,6 @@ pub fn propagate_modifiers(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[allow(dead_code)]
|
|
||||||
Ok(modifiers
|
Ok(modifiers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(key, val)| TransformEntry::from_input(*key, *val))
|
.map(|(key, val)| TransformEntry::from_input(*key, *val))
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::math::{self as math, Bounds, Matrix, Point, Vector, VectorExt};
|
use crate::math::{self as math, Bounds, Matrix, Point, Vector, VectorExt};
|
||||||
use crate::shapes::{
|
use crate::shapes::{
|
||||||
@ -123,7 +121,7 @@ struct ChildAxis {
|
|||||||
max_across_size: f32,
|
max_across_size: f32,
|
||||||
is_fill_main: bool,
|
is_fill_main: bool,
|
||||||
is_fill_across: bool,
|
is_fill_across: bool,
|
||||||
z_index: i32,
|
_z_index: i32, // unused
|
||||||
bounds: Bounds,
|
bounds: Bounds,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +144,7 @@ impl ChildAxis {
|
|||||||
max_across_size: layout_item.and_then(|i| i.max_h).unwrap_or(MAX_SIZE),
|
max_across_size: layout_item.and_then(|i| i.max_h).unwrap_or(MAX_SIZE),
|
||||||
is_fill_main: child.is_layout_horizontal_fill(),
|
is_fill_main: child.is_layout_horizontal_fill(),
|
||||||
is_fill_across: child.is_layout_vertical_fill(),
|
is_fill_across: child.is_layout_vertical_fill(),
|
||||||
z_index: layout_item.and_then(|i| i.z_index).unwrap_or(0),
|
_z_index: layout_item.and_then(|i| i.z_index).unwrap_or(0),
|
||||||
bounds: *child_bounds,
|
bounds: *child_bounds,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -164,7 +162,7 @@ impl ChildAxis {
|
|||||||
max_main_size: layout_item.and_then(|i| i.max_h).unwrap_or(MAX_SIZE),
|
max_main_size: layout_item.and_then(|i| i.max_h).unwrap_or(MAX_SIZE),
|
||||||
is_fill_main: child.is_layout_vertical_fill(),
|
is_fill_main: child.is_layout_vertical_fill(),
|
||||||
is_fill_across: child.is_layout_horizontal_fill(),
|
is_fill_across: child.is_layout_horizontal_fill(),
|
||||||
z_index: layout_item.and_then(|i| i.z_index).unwrap_or(0),
|
_z_index: layout_item.and_then(|i| i.z_index).unwrap_or(0),
|
||||||
bounds: *child_bounds,
|
bounds: *child_bounds,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -115,7 +115,6 @@ impl TextContentSize {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct TextPositionWithAffinity {
|
pub struct TextPositionWithAffinity {
|
||||||
#[allow(dead_code)]
|
|
||||||
pub position_with_affinity: PositionWithAffinity,
|
pub position_with_affinity: PositionWithAffinity,
|
||||||
pub paragraph: usize,
|
pub paragraph: usize,
|
||||||
pub offset: usize,
|
pub offset: usize,
|
||||||
@ -244,9 +243,9 @@ impl TextContentLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TextDecorationSegment {
|
pub struct TextDecorationSegment {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub kind: skia::textlayout::TextDecoration,
|
pub kind: skia::textlayout::TextDecoration,
|
||||||
pub text_style: skia::textlayout::TextStyle,
|
pub text_style: skia::textlayout::TextStyle,
|
||||||
pub y: f32,
|
pub y: f32,
|
||||||
@ -392,16 +391,6 @@ impl TextContent {
|
|||||||
self.bounds = Rect::from_xywh(x, y, w, h);
|
self.bounds = Rect::from_xywh(x, y, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn x(&self) -> f32 {
|
|
||||||
self.bounds.x()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn y(&self) -> f32 {
|
|
||||||
self.bounds.y()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_paragraph(&mut self, paragraph: Paragraph) {
|
pub fn add_paragraph(&mut self, paragraph: Paragraph) {
|
||||||
self.paragraphs.push(paragraph);
|
self.paragraphs.push(paragraph);
|
||||||
self.content_version = self.content_version.wrapping_add(1);
|
self.content_version = self.content_version.wrapping_add(1);
|
||||||
@ -621,10 +610,6 @@ impl TextContent {
|
|||||||
let mut offset_y = 0.0;
|
let mut offset_y = 0.0;
|
||||||
let layout_paragraphs = self.layout.paragraphs.iter().flatten();
|
let layout_paragraphs = self.layout.paragraphs.iter().flatten();
|
||||||
|
|
||||||
// IMPORTANT! I'm keeping this because I think it should be better to have the span index
|
|
||||||
// cached the same way we keep the paragraph index.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
let mut _span_index: usize = 0;
|
|
||||||
for (paragraph_index, layout_paragraph) in layout_paragraphs.enumerate() {
|
for (paragraph_index, layout_paragraph) in layout_paragraphs.enumerate() {
|
||||||
let start_y = offset_y;
|
let start_y = offset_y;
|
||||||
let end_y = offset_y + layout_paragraph.height();
|
let end_y = offset_y + layout_paragraph.height();
|
||||||
@ -652,15 +637,8 @@ impl TextContent {
|
|||||||
// in which span we are.
|
// in which span we are.
|
||||||
let mut computed_position: usize = 0;
|
let mut computed_position: usize = 0;
|
||||||
|
|
||||||
// This could be useful in the future as part of the TextPositionWithAffinity.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
let mut _span_offset: usize = 0;
|
|
||||||
|
|
||||||
// If paragraph has no spans, default to span 0, offset 0
|
// If paragraph has no spans, default to span 0, offset 0
|
||||||
if paragraph.children().is_empty() {
|
if !paragraph.children().is_empty() {
|
||||||
_span_index = 0;
|
|
||||||
_span_offset = 0;
|
|
||||||
} else {
|
|
||||||
for span in paragraph.children() {
|
for span in paragraph.children() {
|
||||||
let length = span.text.chars().count();
|
let length = span.text.chars().count();
|
||||||
let start_position = computed_position;
|
let start_position = computed_position;
|
||||||
@ -670,19 +648,15 @@ impl TextContent {
|
|||||||
// Handle empty spans: if the span is empty and current position
|
// Handle empty spans: if the span is empty and current position
|
||||||
// matches the start, this is the right span
|
// matches the start, this is the right span
|
||||||
if length == 0 && current_position == start_position {
|
if length == 0 && current_position == start_position {
|
||||||
_span_offset = 0;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if start_position <= current_position
|
if start_position <= current_position
|
||||||
&& end_position >= current_position
|
&& end_position >= current_position
|
||||||
{
|
{
|
||||||
_span_offset =
|
|
||||||
position_with_affinity.position as usize - start_position;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
computed_position += length;
|
computed_position += length;
|
||||||
_span_index += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1152,11 +1126,6 @@ impl Paragraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn set_children(&mut self, children: Vec<TextSpan>) {
|
|
||||||
self.children = children;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn children(&self) -> &[TextSpan] {
|
pub fn children(&self) -> &[TextSpan] {
|
||||||
&self.children
|
&self.children
|
||||||
}
|
}
|
||||||
@ -1165,11 +1134,6 @@ impl Paragraph {
|
|||||||
&mut self.children
|
&mut self.children
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn add_span(&mut self, span: TextSpan) {
|
|
||||||
self.children.push(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn line_height(&self) -> f32 {
|
pub fn line_height(&self) -> f32 {
|
||||||
self.line_height
|
self.line_height
|
||||||
}
|
}
|
||||||
@ -1306,11 +1270,6 @@ impl TextSpan {
|
|||||||
self.text = text;
|
self.text = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn fills(&self) -> &[shapes::Fill] {
|
|
||||||
&self.fills
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_style(
|
pub fn to_style(
|
||||||
&self,
|
&self,
|
||||||
content_bounds: &Rect,
|
content_bounds: &Rect,
|
||||||
@ -1415,7 +1374,6 @@ impl TextSpan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct PositionData {
|
pub struct PositionData {
|
||||||
pub paragraph: u32,
|
pub paragraph: u32,
|
||||||
@ -1429,21 +1387,17 @@ pub struct PositionData {
|
|||||||
pub direction: u32,
|
pub direction: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ParagraphLayout {
|
pub struct ParagraphLayout {
|
||||||
pub paragraph: skia::textlayout::Paragraph,
|
pub paragraph: skia::textlayout::Paragraph,
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
pub y: f32,
|
pub y: f32,
|
||||||
pub spans: Vec<crate::shapes::TextSpan>,
|
|
||||||
pub decorations: Vec<TextDecorationSegment>,
|
pub decorations: Vec<TextDecorationSegment>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TextLayoutData {
|
pub struct TextLayoutData {
|
||||||
pub position_data: Vec<PositionData>,
|
pub position_data: Vec<PositionData>,
|
||||||
pub content_rect: Rect,
|
|
||||||
pub paragraphs: Vec<ParagraphLayout>,
|
pub paragraphs: Vec<ParagraphLayout>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1508,12 +1462,6 @@ pub fn calculate_text_layout_data(
|
|||||||
for (i, group_paragraphs) in built_groups.into_iter().enumerate() {
|
for (i, group_paragraphs) in built_groups.into_iter().enumerate() {
|
||||||
// For each paragraph in the group (e.g., fill, stroke, etc.)
|
// For each paragraph in the group (e.g., fill, stroke, etc.)
|
||||||
for skia_paragraph in group_paragraphs.into_iter() {
|
for skia_paragraph in group_paragraphs.into_iter() {
|
||||||
let spans = if let Some(text_para) = text_paragraphs.get(i) {
|
|
||||||
text_para.children().to_vec()
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate text decorations for this paragraph
|
// Calculate text decorations for this paragraph
|
||||||
let mut decorations = Vec::new();
|
let mut decorations = Vec::new();
|
||||||
let line_metrics = skia_paragraph.get_line_metrics();
|
let line_metrics = skia_paragraph.get_line_metrics();
|
||||||
@ -1580,7 +1528,6 @@ pub fn calculate_text_layout_data(
|
|||||||
paragraph: skia_paragraph,
|
paragraph: skia_paragraph,
|
||||||
x,
|
x,
|
||||||
y: y_accum,
|
y: y_accum,
|
||||||
spans: spans.clone(),
|
|
||||||
decorations,
|
decorations,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1644,10 +1591,8 @@ pub fn calculate_text_layout_data(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let content_rect = Rect::from_xywh(x, base_y + vertical_offset, text_width, total_text_height);
|
|
||||||
TextLayoutData {
|
TextLayoutData {
|
||||||
position_data,
|
position_data,
|
||||||
content_rect,
|
|
||||||
paragraphs: paragraph_layouts,
|
paragraphs: paragraph_layouts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ pub use shapes_pool::{ShapesPool, ShapesPoolMutRef, ShapesPoolRef};
|
|||||||
pub use text_editor::*;
|
pub use text_editor::*;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
use crate::render::FrameType;
|
||||||
use crate::shapes::{grid_layout::grid_cell_data, Shape};
|
use crate::shapes::{grid_layout::grid_cell_data, Shape};
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
use crate::{get_render_state, tiles};
|
use crate::{get_render_state, tiles};
|
||||||
@ -64,11 +65,11 @@ impl State {
|
|||||||
get_render_state().render_from_cache(&self.shapes);
|
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)
|
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)
|
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)
|
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();
|
let render_state = get_render_state();
|
||||||
// If zoom changed (e.g. interrupted zoom render followed by pan), the
|
// If zoom changed (e.g. interrupted zoom render followed by pan), the
|
||||||
// tile index may be stale for the new viewport position. Rebuild the
|
// tile index may be stale for the new viewport position. Rebuild the
|
||||||
@ -92,12 +93,11 @@ impl State {
|
|||||||
if render_state.zoom_changed() {
|
if render_state.zoom_changed() {
|
||||||
render_state.rebuild_tile_index(&self.shapes);
|
render_state.rebuild_tile_index(&self.shapes);
|
||||||
}
|
}
|
||||||
|
|
||||||
render_state.start_render_loop(None, &self.shapes, timestamp, false)
|
render_state.start_render_loop(None, &self.shapes, timestamp, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<()> {
|
pub fn continue_render_loop(&mut self, timestamp: i32) -> Result<FrameType> {
|
||||||
get_render_state().process_animation_frame(None, &self.shapes, timestamp)
|
get_render_state().continue_render_loop(None, &self.shapes, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_focus_mode(&mut self) {
|
pub fn clear_focus_mode(&mut self) {
|
||||||
|
|||||||
@ -426,8 +426,6 @@ impl TextEditorState {
|
|||||||
let Some(last_paragraph) = text_content.paragraphs().last() else {
|
let Some(last_paragraph) = text_content.paragraphs().last() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
#[allow(dead_code)]
|
|
||||||
let _num_spans = last_paragraph.children().len() - 1;
|
|
||||||
let Some(_last_text_span) = last_paragraph.children().last() else {
|
let Some(_last_text_span) = last_paragraph.children().last() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,80 +10,169 @@ impl Tile {
|
|||||||
pub fn from(x: i32, y: i32) -> Self {
|
pub fn from(x: i32, y: i32) -> Self {
|
||||||
Tile(x, y)
|
Tile(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
pub fn x(&self) -> i32 {
|
pub fn x(&self) -> i32 {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
pub fn y(&self) -> i32 {
|
pub fn y(&self) -> i32 {
|
||||||
self.1
|
self.1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get_rect_with_size(&self, tile_size: f32) -> skia::Rect {
|
||||||
|
skia::Rect::from_xywh(
|
||||||
|
self.0 as f32 * tile_size,
|
||||||
|
self.1 as f32 * tile_size,
|
||||||
|
tile_size,
|
||||||
|
tile_size,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get_rect_with_offset(&self, offset: &skia::Point) -> skia::Rect {
|
||||||
|
skia::Rect::from_xywh(
|
||||||
|
self.0 as f32 * TILE_SIZE - offset.x,
|
||||||
|
self.1 as f32 * TILE_SIZE - offset.y,
|
||||||
|
TILE_SIZE,
|
||||||
|
TILE_SIZE,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inclusive tile count on X (matches `contains`: both `x1` and `x2` are included).
|
/// Inclusive tile count on X (matches `contains`: both `x1` and `x2` are included).
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
pub fn columns(&self) -> i32 {
|
pub fn columns(&self) -> i32 {
|
||||||
self.x2() - self.x1() + 1
|
self.x2() - self.x1() + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inclusive tile count on Y (matches `contains`: both `y1` and `y2` are included).
|
/// Inclusive tile count on Y (matches `contains`: both `y1` and `y2` are included).
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
pub fn rows(&self) -> i32 {
|
pub fn rows(&self) -> i32 {
|
||||||
self.y2() - self.y1() + 1
|
self.y2() - self.y1() + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn width(&self) -> i32 {
|
||||||
|
self.x2() - self.x1()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn height(&self) -> i32 {
|
||||||
|
self.y2() - self.y1()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
pub fn contains(&self, tile: &Tile) -> bool {
|
pub fn contains(&self, tile: &Tile) -> bool {
|
||||||
tile.x() >= self.left()
|
tile.x() >= self.left()
|
||||||
&& tile.y() >= self.top()
|
&& tile.y() >= self.top()
|
||||||
&& tile.x() <= self.right()
|
&& tile.x() <= self.right()
|
||||||
&& tile.y() <= self.bottom()
|
&& tile.y() <= self.bottom()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn iter(self, inclusive: bool) -> TileRectIter {
|
||||||
|
TileRectIter::new(self, inclusive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct TileRectIter {
|
||||||
|
rect: TileRect,
|
||||||
|
inclusive: bool,
|
||||||
|
index: i32,
|
||||||
|
total: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TileRectIter {
|
||||||
|
fn new(rect: TileRect, inclusive: bool) -> Self {
|
||||||
|
let width = rect.width() + if inclusive { 1 } else { 0 };
|
||||||
|
let height = rect.height() + if inclusive { 1 } else { 0 };
|
||||||
|
Self {
|
||||||
|
rect,
|
||||||
|
inclusive,
|
||||||
|
index: 0,
|
||||||
|
total: width * height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for TileRectIter {
|
||||||
|
type Item = Tile;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.index >= self.total {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = self.rect.width() + if self.inclusive { 1 } else { 0 };
|
||||||
|
|
||||||
|
let x = self.rect.left() + self.index % width;
|
||||||
|
let y = self.rect.top() + self.index / width;
|
||||||
|
|
||||||
|
self.index += 1;
|
||||||
|
|
||||||
|
Some(Tile::from(x, y))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -95,19 +184,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) {
|
||||||
@ -122,6 +211,7 @@ impl TileViewbox {
|
|||||||
|
|
||||||
pub const TILE_SIZE: f32 = 512.;
|
pub const TILE_SIZE: f32 = 512.;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
pub fn get_tile_dimensions() -> skia::ISize {
|
pub fn get_tile_dimensions() -> skia::ISize {
|
||||||
(TILE_SIZE as i32, TILE_SIZE as i32).into()
|
(TILE_SIZE as i32, TILE_SIZE as i32).into()
|
||||||
}
|
}
|
||||||
@ -136,22 +226,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,7 +258,7 @@ pub fn get_tile_rect(tile: Tile, scale: f32) -> skia::Rect {
|
|||||||
skia::Rect::from_xywh(tx, ty, ts, ts)
|
skia::Rect::from_xywh(tx, ty, ts, ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This structure is usseful to keep all the shape uuids by shape id.
|
// This structure is useful to keep all the shape uuids by shape id.
|
||||||
pub struct TileHashMap {
|
pub struct TileHashMap {
|
||||||
grid: HashMap<Tile, HashSet<Uuid>>,
|
grid: HashMap<Tile, HashSet<Uuid>>,
|
||||||
index: HashMap<Uuid, HashSet<Tile>>,
|
index: HashMap<Uuid, HashSet<Tile>>,
|
||||||
@ -186,6 +272,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)
|
||||||
}
|
}
|
||||||
@ -219,7 +312,7 @@ impl TileHashMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const VIEWPORT_DEFAULT_CAPACITY: usize = 24 * 12;
|
const VIEWPORT_DEFAULT_CAPACITY: usize = 24 * 12;
|
||||||
const VIEWPORT_SPIRAL_DEFAULT_CAPACITY: usize = 64;
|
const VIEWPORT_SPIRAL_DEFAULT_CAPACITY: usize = VIEWPORT_DEFAULT_CAPACITY;
|
||||||
|
|
||||||
/// Cached spiral of tile offsets for a given grid size.
|
/// Cached spiral of tile offsets for a given grid size.
|
||||||
///
|
///
|
||||||
@ -315,6 +408,10 @@ pub struct PendingTiles {
|
|||||||
pub list: Vec<Tile>,
|
pub list: Vec<Tile>,
|
||||||
pub spiral: TileSpiral,
|
pub spiral: TileSpiral,
|
||||||
pub spiral_rect: TileRect,
|
pub spiral_rect: TileRect,
|
||||||
|
pub visible_cached: Vec<Tile>,
|
||||||
|
pub visible_uncached: Vec<Tile>,
|
||||||
|
pub interest_cached: Vec<Tile>,
|
||||||
|
pub interest_uncached: Vec<Tile>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PendingTiles {
|
impl PendingTiles {
|
||||||
@ -323,6 +420,10 @@ impl PendingTiles {
|
|||||||
list: Vec::with_capacity(VIEWPORT_DEFAULT_CAPACITY),
|
list: Vec::with_capacity(VIEWPORT_DEFAULT_CAPACITY),
|
||||||
spiral: TileSpiral::new(),
|
spiral: TileSpiral::new(),
|
||||||
spiral_rect: TileRect::empty(),
|
spiral_rect: TileRect::empty(),
|
||||||
|
visible_cached: Vec::with_capacity(VIEWPORT_DEFAULT_CAPACITY),
|
||||||
|
visible_uncached: Vec::with_capacity(VIEWPORT_DEFAULT_CAPACITY),
|
||||||
|
interest_cached: Vec::with_capacity(VIEWPORT_DEFAULT_CAPACITY),
|
||||||
|
interest_uncached: Vec::with_capacity(VIEWPORT_DEFAULT_CAPACITY),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,10 +457,10 @@ impl PendingTiles {
|
|||||||
// 2. visible + uncached (user sees these, render next)
|
// 2. visible + uncached (user sees these, render next)
|
||||||
// 3. interest + cached (pre-rendered area, blit from cache)
|
// 3. interest + cached (pre-rendered area, blit from cache)
|
||||||
// 4. interest + uncached (lowest priority - background pre-render)
|
// 4. interest + uncached (lowest priority - background pre-render)
|
||||||
let mut visible_cached = Vec::new();
|
self.visible_cached.clear();
|
||||||
let mut visible_uncached = Vec::new();
|
self.visible_uncached.clear();
|
||||||
let mut interest_cached = Vec::new();
|
self.interest_cached.clear();
|
||||||
let mut interest_uncached = Vec::new();
|
self.interest_uncached.clear();
|
||||||
|
|
||||||
// Compute the scheduling center explicitly (inclusive range).
|
// Compute the scheduling center explicitly (inclusive range).
|
||||||
// This avoids relying on `TileRect::center_x/center_y` semantics, which may be used
|
// This avoids relying on `TileRect::center_x/center_y` semantics, which may be used
|
||||||
@ -374,19 +475,19 @@ impl PendingTiles {
|
|||||||
let is_cached = surfaces.has_cached_tile_surface(tile);
|
let is_cached = surfaces.has_cached_tile_surface(tile);
|
||||||
|
|
||||||
match (is_visible, is_cached) {
|
match (is_visible, is_cached) {
|
||||||
(true, true) => visible_cached.push(tile),
|
(true, true) => self.visible_cached.push(tile),
|
||||||
(true, false) => visible_uncached.push(tile),
|
(true, false) => self.visible_uncached.push(tile),
|
||||||
(false, true) => interest_cached.push(tile),
|
(false, true) => self.interest_cached.push(tile),
|
||||||
(false, false) => interest_uncached.push(tile),
|
(false, false) => self.interest_uncached.push(tile),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build final list with lowest priority first (they get popped last)
|
// Build final list with lowest priority first (they get popped last)
|
||||||
// Order: interest_uncached, interest_cached, visible_uncached, visible_cached
|
// Order: interest_uncached, interest_cached, visible_uncached, visible_cached
|
||||||
self.list.extend(interest_uncached);
|
self.list.extend(self.interest_uncached.iter());
|
||||||
self.list.extend(interest_cached);
|
self.list.extend(self.interest_cached.iter());
|
||||||
self.list.extend(visible_uncached);
|
self.list.extend(self.visible_uncached.iter());
|
||||||
self.list.extend(visible_cached);
|
self.list.extend(self.visible_cached.iter());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop(&mut self) -> Option<Tile> {
|
pub fn pop(&mut self) -> Option<Tile> {
|
||||||
|
|||||||
@ -1,62 +1,86 @@
|
|||||||
use skia_safe::Rect;
|
use crate::math::{Matrix, Point, Rect, Size};
|
||||||
|
use std::ops::Mul;
|
||||||
use crate::math::{Matrix, Point};
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub(crate) struct Viewbox {
|
pub(crate) struct Viewbox {
|
||||||
pub pan_x: f32,
|
pub pan: Point,
|
||||||
pub pan_y: f32,
|
pub size: Size,
|
||||||
pub width: f32,
|
|
||||||
pub height: f32,
|
|
||||||
pub zoom: f32,
|
pub zoom: f32,
|
||||||
|
pub dpr: f32,
|
||||||
pub area: Rect,
|
pub area: Rect,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Viewbox {
|
impl Default for Viewbox {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
pan_x: 0.,
|
pan: Point::new(0.0, 0.0),
|
||||||
pan_y: 0.,
|
size: Size::new(0.0, 0.0),
|
||||||
width: 0.0,
|
|
||||||
height: 0.0,
|
|
||||||
zoom: 1.0,
|
zoom: 1.0,
|
||||||
|
dpr: 1.0,
|
||||||
area: Rect::new_empty(),
|
area: Rect::new_empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
impl Viewbox {
|
impl Viewbox {
|
||||||
pub fn new(width: f32, height: f32) -> Self {
|
pub fn new(width: f32, height: f32) -> Self {
|
||||||
let area = Rect::from_xywh(0., 0., width, height);
|
let size = Size::new(width, height);
|
||||||
|
let area = Rect::from_size(size);
|
||||||
Self {
|
Self {
|
||||||
width,
|
size,
|
||||||
height,
|
|
||||||
area,
|
area,
|
||||||
..Self::default()
|
..Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dpr_width(&self) -> f32 {
|
||||||
|
self.size.width * self.dpr
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dpr_height(&self) -> f32 {
|
||||||
|
self.size.height * self.dpr
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> f32 {
|
||||||
|
self.size.width
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> f32 {
|
||||||
|
self.size.height
|
||||||
|
}
|
||||||
|
|
||||||
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_x = pan_x;
|
self.pan.set(pan_x, pan_y);
|
||||||
self.pan_y = pan_y;
|
|
||||||
self.zoom = zoom;
|
self.zoom = zoom;
|
||||||
self.area.set_xywh(
|
self.area.set_xywh(
|
||||||
-self.pan_x,
|
-self.pan.x,
|
||||||
-self.pan_y,
|
-self.pan.y,
|
||||||
self.width / self.zoom,
|
self.size.width / self.zoom,
|
||||||
self.height / self.zoom,
|
self.size.height / self.zoom,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_wh(&mut self, width: f32, height: f32) {
|
pub fn set_wh(&mut self, width: f32, height: f32) {
|
||||||
self.width = width;
|
self.size.set(width, height);
|
||||||
self.height = height;
|
|
||||||
self.area
|
self.area
|
||||||
.set_wh(self.width / self.zoom, self.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 {
|
||||||
Point::new(self.pan_x, self.pan_y)
|
self.pan
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn zoom(&self) -> f32 {
|
pub fn zoom(&self) -> f32 {
|
||||||
|
|||||||
@ -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_export]
|
||||||
macro_rules! notify_tiles_render_complete {
|
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 notify_tiles_render_complete;
|
||||||
pub use request_animation_frame;
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user