mimo
This commit is contained in:
@@ -1720,6 +1720,16 @@ impl Engine {
|
||||
layer.effects_dirty.store(true, std::sync::atomic::Ordering::Release);
|
||||
layer.dirty = true;
|
||||
self.document.composite_dirty = true;
|
||||
// Mark the full canvas as dirty so the next render recomposites.
|
||||
// Without this, dirty_bounds stays None and partial_composite forces
|
||||
// a full-canvas recomposite anyway — but other property ops set this
|
||||
// explicitly so we should too for consistency and correctness.
|
||||
let w = self.document.canvas_width;
|
||||
let h = self.document.canvas_height;
|
||||
self.document.dirty_bounds = Some([0, 0, w, h]);
|
||||
// Invalidate the below-layer composite cache since this layer's
|
||||
// pixels changed via the effects pipeline.
|
||||
self.below_cache_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1836,6 +1846,10 @@ impl Engine {
|
||||
}
|
||||
if did_remove {
|
||||
self.document.composite_dirty = true;
|
||||
let w = self.document.canvas_width;
|
||||
let h = self.document.canvas_height;
|
||||
self.document.dirty_bounds = Some([0, 0, w, h]);
|
||||
self.below_cache_dirty = true;
|
||||
}
|
||||
if empty_styles {
|
||||
self.raw_pixel_backup.remove(&id);
|
||||
|
||||
@@ -233,6 +233,10 @@ impl Engine {
|
||||
|
||||
// Pass 2: Apply layer effects. Restores from backup first so that each
|
||||
// slider edit always applies on top of the original untouched pixels.
|
||||
// CRITICAL: Do NOT modify layer.pixels with effects output. layer.pixels
|
||||
// must always contain the raw drawing data so that new strokes are drawn
|
||||
// on top of clean pixels (not on effects-applied pixels). The composite
|
||||
// uses effects_cache for layers with effects, not layer.pixels.
|
||||
for layer in &mut self.document.layers {
|
||||
if layer.effects.is_empty() && layer.styles.is_empty() { continue; }
|
||||
if layer.effects_dirty.load(std::sync::atomic::Ordering::Acquire) {
|
||||
@@ -258,7 +262,9 @@ impl Engine {
|
||||
width: layer.width,
|
||||
height: layer.height,
|
||||
});
|
||||
layer.pixels = processed;
|
||||
// DON'T: layer.pixels = processed;
|
||||
// layer.pixels stays as raw drawing data. The composite pipeline
|
||||
// in tiled.rs reads from effects_cache when it exists.
|
||||
layer.effects_dirty.store(false, std::sync::atomic::Ordering::Release);
|
||||
layer.dirty = true;
|
||||
}
|
||||
|
||||
@@ -127,9 +127,9 @@ impl Engine {
|
||||
tip.density,
|
||||
);
|
||||
layer.dirty = true;
|
||||
// Effects are expensive (33MB clone + apply_layer_effects on 4K).
|
||||
// Only mark the layer as needing an effects pass if it actually
|
||||
// has any effects or editable styles.
|
||||
// Mark effects_dirty so the effects pipeline re-runs after the
|
||||
// stroke ends. This is safe because apply_effects_and_sync_tiles
|
||||
// no longer modifies layer.pixels — it only updates effects_cache.
|
||||
if !layer.effects.is_empty() || !layer.styles.is_empty() {
|
||||
layer.effects_dirty.store(true, std::sync::atomic::Ordering::Release);
|
||||
}
|
||||
@@ -166,8 +166,6 @@ impl Engine {
|
||||
self.stroke_before_buf.as_deref(),
|
||||
);
|
||||
layer.dirty = true;
|
||||
// Avoid triggering the expensive effects pass for layers that
|
||||
// have no effects/styles.
|
||||
if !layer.effects.is_empty() || !layer.styles.is_empty() {
|
||||
layer.effects_dirty.store(true, std::sync::atomic::Ordering::Release);
|
||||
}
|
||||
@@ -200,8 +198,6 @@ impl Engine {
|
||||
let px_color = [color[0], color[1], color[2], (color[3] as f32 * tip.opacity * pressure).round() as u8];
|
||||
draw_line(layer, x0, y0, x1, y1, px_color, tip.size, mask_ref);
|
||||
layer.dirty = true;
|
||||
// Only request an effects re-render if the layer actually has
|
||||
// effects or editable styles.
|
||||
if !layer.effects.is_empty() || !layer.styles.is_empty() {
|
||||
layer.effects_dirty.store(true, std::sync::atomic::Ordering::Release);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user