gemini feat: optimize drawing performance by caching layer styles during strokes and improve UI/UX for layer styles and menus, improved drop shadow

This commit is contained in:
2026-07-13 14:37:22 +03:00
parent 356fac571d
commit 2284a7d81f
50 changed files with 777 additions and 239 deletions
+28 -2
View File
@@ -126,7 +126,7 @@ impl Engine {
pub fn begin_stroke(&mut self, layer_id: u64, x: f32, y: f32) {
self.last_stroke_pos = Some((x, y, 1.0));
self.sketch_history.clear();
if let Some(layer) = self.document.get_layer_by_id(layer_id) {
if let Some(layer) = self.document.get_layer_by_id_mut(layer_id) {
let layer_pixels = (layer.width * layer.height * 4) as usize; // RGBA byte size
let layer_size = (layer.width * layer.height) as usize; // pixel count (for mask)
@@ -143,6 +143,19 @@ impl Engine {
}
}
// Backup and clear layer.effects to disable expensive effect compositing during the stroke
if !layer.effects.is_empty() {
self.stroke_effects_backup = Some(layer.effects.clone());
layer.effects.clear();
// Clear the cache so it stops rendering the old effects immediately
*layer.effects_cache.lock().unwrap() = None;
}
if !layer.styles.is_empty() {
self.stroke_styles_backup = Some(layer.styles.clone());
layer.styles.clear();
*layer.effects_cache.lock().unwrap() = None;
}
// Initialize last_stroke_bounds with brush radius around first point
// Used for sub-rect snapshot in end_stroke (avoid 33MB clone on 4K)
let tip = &self.current_tip;
@@ -196,9 +209,20 @@ impl Engine {
if let Some((id, before_shapes)) = self.stroke_before.take() {
if id == layer_id {
if let Some(idx) = self.document.layer_index_by_id(layer_id) {
let layer = &self.document.layers[idx];
let layer = &mut self.document.layers[idx];
let lw = layer.width;
let layer_pixels = (layer.width * layer.height * 4) as usize; // RGBA byte size
// Restore layer effects from backup so they are re-composited correctly
if let Some(backup) = self.stroke_effects_backup.take() {
layer.effects = backup;
}
if let Some(backup) = self.stroke_styles_backup.take() {
layer.styles = backup;
}
if !layer.effects.is_empty() || !layer.styles.is_empty() {
layer.effects_dirty.store(true, std::sync::atomic::Ordering::Release);
}
// Take the pooled before buffer (no allocation)
log::debug!("[end_stroke] layer_pixels={}, stroke_before_buf={:?}", layer_pixels, self.stroke_before_buf.as_ref().map(|b| b.len()));
@@ -286,6 +310,7 @@ impl Engine {
self.last_stroke_bounds = None;
self.last_stroke_pos = None;
self.below_cache_dirty = false;
self.document.composite_dirty = true;
if let Some(mask) = &mut self.active_stroke_mask {
mask.fill(0);
}
@@ -297,6 +322,7 @@ impl Engine {
self.last_stroke_pos = None;
self.last_stroke_bounds = None;
self.below_cache_dirty = false;
self.document.composite_dirty = true;
log::trace!("[end_stroke_async] fallback cleanup done, below_cache retained for reuse");
if let Some(mask) = &mut self.active_stroke_mask {
mask.fill(0);