From 01913a7cb9e1f9190fd3f9d2d047cbb88b9aa4ff Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Sun, 14 Aug 2011 09:08:30 +0200 Subject: Correctly invalidate cache of objects with background-accessing filters (bzr r10347.1.32) --- src/display/drawing-item.cpp | 197 ++++++++++++++++++++++--------------- src/display/drawing-item.h | 8 +- src/display/drawing-shape.cpp | 20 ++-- src/display/nr-filter-blend.cpp | 11 +++ src/display/nr-filter-blend.h | 1 + src/display/nr-filter-flood.h | 1 + src/display/nr-filter-merge.cpp | 11 +++ src/display/nr-filter-merge.h | 1 + src/display/nr-filter-primitive.h | 9 ++ src/display/nr-filter-turbulence.h | 1 + src/display/nr-filter.cpp | 10 ++ src/display/nr-filter.h | 3 + 12 files changed, 188 insertions(+), 85 deletions(-) (limited to 'src') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index ac0d1be1e..f28894c14 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -56,6 +56,8 @@ DrawingItem::DrawingItem(Drawing &drawing) , _cache(NULL) , _state(0) , _child_type(CHILD_ORPHAN) + , _background_new(0) + , _background_accumulate(0) , _visible(true) , _sensitive(true) , _cached(0) @@ -300,10 +302,9 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne bool outline = _drawing.outline(); // Set reset flags according to propagation status - if (_propagate) { - reset |= ~_state; - _propagate = FALSE; - } + reset |= _propagate_state; + _propagate_state = 0; + _state &= ~reset; // reset state of this item if ((~_state & flags) == 0) return; // nothing to do @@ -323,80 +324,91 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne _ctm = child_ctx.ctm; // update _bbox - unsigned old_state = _state; + unsigned to_update = _state ^ flags; _state = _updateItem(area, child_ctx, flags, reset); - // compute drawbox - if (_filter && render_filters && _item_bbox) { - _drawbox = _filter->compute_drawbox(this, *_item_bbox); - } else { - _drawbox = _bbox; - } - - // Clipping - if (_clip) { - _clip->update(area, child_ctx, flags, reset); - if (outline) { - _bbox.unionWith(_clip->_bbox); + if (to_update & STATE_BBOX) { + // compute drawbox + if (_filter && render_filters && _item_bbox) { + _drawbox = _filter->compute_drawbox(this, *_item_bbox); } else { - _drawbox.intersectWith(_clip->_bbox); + _drawbox = _bbox; } - } - // masking - if (_mask) { - _mask->update(area, child_ctx, flags, reset); - if (outline) { - _bbox.unionWith(_mask->_bbox); - } else { - // for masking, we need full drawbox of mask - _drawbox.intersectWith(_mask->_drawbox); + + // Clipping + if (_clip) { + _clip->update(area, child_ctx, flags, reset); + if (outline) { + _bbox.unionWith(_clip->_bbox); + } else { + _drawbox.intersectWith(_clip->_bbox); + } + } + // Masking + if (_mask) { + _mask->update(area, child_ctx, flags, reset); + if (outline) { + _bbox.unionWith(_mask->_bbox); + } else { + // for masking, we need full drawbox of mask + _drawbox.intersectWith(_mask->_drawbox); + } } } - // Update cache score for this item - if (_has_cache_iterator) { - // remove old score information - _drawing._candidate_items.erase(_cache_iterator); - _has_cache_iterator = false; - } - double score = _cacheScore(); - if (score >= _drawing._cache_score_threshold) { - CacheRecord cr; - cr.score = score; - // if _cacheRect() is empty, a negative score will be returned from _cacheScore(), - // so this will not execute (cache score threshold must be positive) - cr.cache_size = _cacheRect()->area() * 4; - cr.item = this; - _drawing._candidate_items.push_back(cr); - _cache_iterator = --_drawing._candidate_items.end(); - _has_cache_iterator = true; - } - - /* Update cache if enabled. - * General note: here we only tell the cache how it has to transform - * during the render phase. The transformation is deferred because - * after the update the item can have its caching turned off, - * e.g. because its filter was removed. This way we avoid tempoerarily - * using more memory than the cache budget */ - if (_cache) { - Geom::OptIntRect cl = _cacheRect(); - if (_visible && cl) { // never create cache for invisible items - // this takes care of invalidation on transform - _cache->scheduleTransform(*cl, ctm_change); - } else { - // Destroy cache for this item - outside of canvas or invisible. - // The opposite transition (invisible -> visible or object - // entering the canvas) is handled during the render phase - delete _cache; - _cache = NULL; + if (to_update & STATE_CACHE) { + // Update cache score for this item + if (_has_cache_iterator) { + // remove old score information + _drawing._candidate_items.erase(_cache_iterator); + _has_cache_iterator = false; + } + double score = _cacheScore(); + if (score >= _drawing._cache_score_threshold) { + CacheRecord cr; + cr.score = score; + // if _cacheRect() is empty, a negative score will be returned from _cacheScore(), + // so this will not execute (cache score threshold must be positive) + cr.cache_size = _cacheRect()->area() * 4; + cr.item = this; + _drawing._candidate_items.push_back(cr); + _cache_iterator = --_drawing._candidate_items.end(); + _has_cache_iterator = true; + } + + /* Update cache if enabled. + * General note: here we only tell the cache how it has to transform + * during the render phase. The transformation is deferred because + * after the update the item can have its caching turned off, + * e.g. because its filter was removed. This way we avoid tempoerarily + * using more memory than the cache budget */ + if (_cache) { + Geom::OptIntRect cl = _cacheRect(); + if (_visible && cl) { // never create cache for invisible items + // this takes care of invalidation on transform + _cache->scheduleTransform(*cl, ctm_change); + } else { + // Destroy cache for this item - outside of canvas or invisible. + // The opposite transition (invisible -> visible or object + // entering the canvas) is handled during the render phase + delete _cache; + _cache = NULL; + } } } - // now that we know drawbox, dirty the corresponding rect on canvas - // unless filtered, groups do not need to render by themselves, only their members - if (!is_drawing_group(this) || (_filter && render_filters)) { - // mark for rendering if the item becomes renderable - if ((old_state ^ _state) & STATE_RENDER) { + if (to_update & STATE_BACKGROUND) { + // Update _background_accumulate flag + // The code below correctly passes information from _background_new down the tree + _background_accumulate = _background_new; + if (_child_type == CHILD_NORMAL && _parent->_background_accumulate) + _background_accumulate = true; + } + + if (to_update & STATE_RENDER) { + // now that we know drawbox, dirty the corresponding rect on canvas + // unless filtered, groups do not need to render by themselves, only their members + if (!is_drawing_group(this) || (_filter && render_filters)) { _markForRendering(); } } @@ -720,14 +732,37 @@ DrawingItem::_markForRendering() if (!dirty) return; // dirty the caches of all parents + DrawingItem *bkg_root = NULL; + for (DrawingItem *i = this; i; i = i->_parent) { if (i->_cached && i->_cache) { i->_cache->markDirty(*dirty); } + if (i->_background_accumulate) { + bkg_root = i; + } + } + + if (bkg_root) { + bkg_root->_invalidateFilterBackground(*dirty); } _drawing.signal_request_render.emit(*dirty); } +void +DrawingItem::_invalidateFilterBackground(Geom::IntRect const &area) +{ + if (!_drawbox.intersects(area)) return; + + if (_cache && _filter && _filter->uses_background()) { + _cache->markDirty(area); + } + + for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) { + i->_invalidateFilterBackground(area); + } +} + /** @brief Marks the item as needing a recomputation of internal data. * * This mechanism avoids traversing the entire rendering tree (which could be vast) @@ -745,10 +780,9 @@ DrawingItem::_markForRendering() void DrawingItem::_markForUpdate(unsigned flags, bool propagate) { - // we can't simply assign because a previous markForUpdate call - // could have had propagate=true even if this one has propagate=false - if (propagate) - _propagate = true; + if (propagate) { + _propagate_state |= flags; + } if (_state & flags) { _state &= ~flags; @@ -780,16 +814,23 @@ DrawingItem::_setStyleCommon(SPStyle *&_style, SPStyle *style) _filter = NULL; } - /* - if (style && style->enable_background.set - && style->enable_background.value == SP_CSS_BACKGROUND_NEW) { - _background_new = true; - }*/ + if (style && style->enable_background.set) { + if (style->enable_background.value == SP_CSS_BACKGROUND_NEW && !_background_new) { + _background_new = true; + _markForUpdate(STATE_BACKGROUND, true); + } else if (style->enable_background.value == SP_CSS_BACKGROUND_ACCUMULATE && _background_new) { + _background_new = false; + _markForUpdate(STATE_BACKGROUND, true); + } + } - // TODO: STATE_ALL unsets too much _markForUpdate(STATE_ALL, false); } +/** @brief Compute the caching score. + * + * Higher scores mean the item is more aggresively prioritized for automatic + * caching by Inkscape::Drawing. */ double DrawingItem::_cacheScore() { diff --git a/src/display/drawing-item.h b/src/display/drawing-item.h index a50e3ef03..6d142c061 100644 --- a/src/display/drawing-item.h +++ b/src/display/drawing-item.h @@ -62,7 +62,8 @@ public: STATE_CACHE = (1<<1), // cache extents and clean area are up-to-date STATE_PICK = (1<<2), // can process pick requests STATE_RENDER = (1<<3), // can be rendered - STATE_ALL = (1<<4)-1 + STATE_BACKGROUND = (1<<4), // filter background data is up to date + STATE_ALL = (1<<5)-1 }; enum PickFlags { PICK_NORMAL = 0, // normal pick @@ -123,6 +124,7 @@ protected: void _renderOutline(DrawingContext &ct, Geom::IntRect const &area, unsigned flags); void _markForUpdate(unsigned state, bool propagate); void _markForRendering(); + void _invalidateFilterBackground(Geom::IntRect const &area); void _setStyleCommon(SPStyle *&_style, SPStyle *style); double _cacheScore(); Geom::OptIntRect _cacheRect(); @@ -166,7 +168,11 @@ protected: CacheList::iterator _cache_iterator; unsigned _state : 8; + unsigned _propagate_state : 8; unsigned _child_type : 3; // see ChildType enum + unsigned _background_new : 1; ///< Whether enable-background: new is set for this element + unsigned _background_accumulate : 1; ///< Whether this element accumulates background + /// (has any ancestor with enable-background: new) unsigned _visible : 1; unsigned _sensitive : 1; ///< Whether this item responds to events unsigned _cached : 1; ///< Whether the rendering is stored for reuse diff --git a/src/display/drawing-shape.cpp b/src/display/drawing-shape.cpp index b333b50f8..1b201927f 100644 --- a/src/display/drawing-shape.cpp +++ b/src/display/drawing-shape.cpp @@ -244,8 +244,9 @@ DrawingShape::_pickItem(Geom::Point const &p, double delta, unsigned flags) if (!_style) return NULL; bool outline = _drawing.outline(); + bool pick_as_clip = flags & PICK_AS_CLIP; - if (SP_SCALE24_TO_FLOAT(_style->opacity.value) == 0 && !outline) + if (SP_SCALE24_TO_FLOAT(_style->opacity.value) == 0 && !outline && !pick_as_clip) // fully transparent, no pick unless outline mode return NULL; @@ -253,9 +254,14 @@ DrawingShape::_pickItem(Geom::Point const &p, double delta, unsigned flags) g_get_current_time (&tstart); double width; - if (outline) { - width = 0.5; + if (pick_as_clip) { + width = 0; // no width should be applied to clip picking + // this overrides display mode and stroke style considerations + } else if (outline) { + width = 0.5; // in outline mode, everything is stroked with the same 0.5px line width } else if (_nrstyle.stroke.type != NRStyle::PAINT_NONE && _nrstyle.stroke.opacity > 1e-3) { + // for normal picking calculate the distance corresponding top the stroke width + // FIXME BUG: this is incorrect for transformed strokes float const scale = _ctm.descrim(); width = std::max(0.125f, _nrstyle.stroke_width * scale) / 2; } else { @@ -264,10 +270,12 @@ DrawingShape::_pickItem(Geom::Point const &p, double delta, unsigned flags) double dist = Geom::infinity(); int wind = 0; - bool needfill = (flags & PICK_AS_CLIP) || (_nrstyle.fill.type != NRStyle::PAINT_NONE - && _nrstyle.fill.opacity > 1e-3 && !outline); - bool wind_evenodd = (flags & PICK_AS_CLIP) ? (_style->clip_rule.computed == SP_WIND_RULE_EVENODD) : (_style->fill_rule.computed == SP_WIND_RULE_EVENODD); + bool needfill = pick_as_clip || (_nrstyle.fill.type != NRStyle::PAINT_NONE && + _nrstyle.fill.opacity > 1e-3 && !outline); + bool wind_evenodd = pick_as_clip ? (_style->clip_rule.computed == SP_WIND_RULE_EVENODD) : + (_style->fill_rule.computed == SP_WIND_RULE_EVENODD); + // actual shape picking if (_drawing.arena()) { Geom::Rect viewbox = _drawing.arena()->item.canvas->getViewbox(); viewbox.expandBy (width); diff --git a/src/display/nr-filter-blend.cpp b/src/display/nr-filter-blend.cpp index 99a142b44..267883b4b 100644 --- a/src/display/nr-filter-blend.cpp +++ b/src/display/nr-filter-blend.cpp @@ -201,6 +201,17 @@ double FilterBlend::complexity(Geom::Affine const &) return 1.1; } +bool FilterBlend::uses_background() +{ + if (_input == NR_FILTER_BACKGROUNDIMAGE || _input == NR_FILTER_BACKGROUNDALPHA || + _input2 == NR_FILTER_BACKGROUNDIMAGE || _input2 == NR_FILTER_BACKGROUNDALPHA) + { + return true; + } else { + return false; + } +} + void FilterBlend::set_input(int slot) { _input = slot; } diff --git a/src/display/nr-filter-blend.h b/src/display/nr-filter-blend.h index 5f71d468d..957d3cfc8 100644 --- a/src/display/nr-filter-blend.h +++ b/src/display/nr-filter-blend.h @@ -40,6 +40,7 @@ public: virtual void render_cairo(FilterSlot &slot); virtual bool can_handle_affine(Geom::Affine const &); virtual double complexity(Geom::Affine const &ctm); + virtual bool uses_background(); virtual void set_input(int slot); virtual void set_input(int input, int slot); diff --git a/src/display/nr-filter-flood.h b/src/display/nr-filter-flood.h index c87bf6d8f..f744e9f48 100644 --- a/src/display/nr-filter-flood.h +++ b/src/display/nr-filter-flood.h @@ -29,6 +29,7 @@ public: virtual bool can_handle_affine(Geom::Affine const &); virtual void area_enlarge(NRRectL &area, Geom::Affine const &trans); virtual double complexity(Geom::Affine const &ctm); + virtual bool uses_background() { return false; } virtual void set_opacity(double o); virtual void set_color(guint32 c); diff --git a/src/display/nr-filter-merge.cpp b/src/display/nr-filter-merge.cpp index 6042da018..28ac19a19 100644 --- a/src/display/nr-filter-merge.cpp +++ b/src/display/nr-filter-merge.cpp @@ -72,6 +72,17 @@ double FilterMerge::complexity(Geom::Affine const &) return 1.02; } +bool FilterMerge::uses_background() +{ + for (int i = 0; i < _input_image.size(); ++i) { + int input = _input_image[i]; + if (input == NR_FILTER_BACKGROUNDIMAGE || input == NR_FILTER_BACKGROUNDALPHA) { + return true; + } + } + return false; +} + void FilterMerge::set_input(int slot) { _input_image[0] = slot; } diff --git a/src/display/nr-filter-merge.h b/src/display/nr-filter-merge.h index cedab9086..238f9a3e7 100644 --- a/src/display/nr-filter-merge.h +++ b/src/display/nr-filter-merge.h @@ -27,6 +27,7 @@ public: virtual void render_cairo(FilterSlot &); virtual bool can_handle_affine(Geom::Affine const &); virtual double complexity(Geom::Affine const &ctm); + virtual bool uses_background(); virtual void set_input(int input); virtual void set_input(int input, int slot); diff --git a/src/display/nr-filter-primitive.h b/src/display/nr-filter-primitive.h index 259a25e7e..501d76447 100644 --- a/src/display/nr-filter-primitive.h +++ b/src/display/nr-filter-primitive.h @@ -12,6 +12,7 @@ #define SEEN_NR_FILTER_PRIMITIVE_H #include <2geom/forward.h> +#include "display/nr-filter-types.h" #include "svg/svg-length.h" struct NRRectL; @@ -66,6 +67,14 @@ public: // returns cache score factor, reflecting the cost of rendering this filter // this should return how many times slower this primitive is that normal rendering virtual double complexity(Geom::Affine const &/*ctm*/) { return 1.0; } + + virtual bool uses_background() { + if (_input == NR_FILTER_BACKGROUNDIMAGE || _input == NR_FILTER_BACKGROUNDALPHA) { + return true; + } else { + return false; + } + } /** * Sets the filter primitive subregion. Passing an unset length diff --git a/src/display/nr-filter-turbulence.h b/src/display/nr-filter-turbulence.h index 9f824ef48..0b451d355 100644 --- a/src/display/nr-filter-turbulence.h +++ b/src/display/nr-filter-turbulence.h @@ -46,6 +46,7 @@ public: virtual void render_cairo(FilterSlot &slot); virtual double complexity(Geom::Affine const &ctm); + virtual bool uses_background() { return false; } void set_baseFrequency(int axis, double freq); void set_numOctaves(int num); diff --git a/src/display/nr-filter.cpp b/src/display/nr-filter.cpp index ef9ac5be6..ae50e641b 100644 --- a/src/display/nr-filter.cpp +++ b/src/display/nr-filter.cpp @@ -294,6 +294,16 @@ double Filter::complexity(Geom::Affine const &ctm) return factor; } +bool Filter::uses_background() +{ + for (unsigned i = 0 ; i < _primitive.size() ; i++) { + if (_primitive[i] && _primitive[i]->uses_background()) { + return true; + } + } + return false; +} + /* Constructor table holds pointers to static methods returning filter * primitives. This table is indexed with FilterPrimitiveType, so that * for example method in _constructor[NR_FILTER_GAUSSIANBLUR] diff --git a/src/display/nr-filter.h b/src/display/nr-filter.h index 7d31e10ce..87a0fae94 100644 --- a/src/display/nr-filter.h +++ b/src/display/nr-filter.h @@ -167,6 +167,9 @@ public: // returns cache score factor double complexity(Geom::Affine const &ctm); + // says whether the filter accesses any of the background images + bool uses_background(); + /** Creates a new filter with space for one filter element */ Filter(); /** -- cgit v1.2.3