=== modified file 'CMakeScripts/DefineDependsandFlags.cmake' --- CMakeScripts/DefineDependsandFlags.cmake 2016-10-01 10:08:49 +0000 +++ CMakeScripts/DefineDependsandFlags.cmake 2017-01-05 02:07:34 +0000 @@ -304,9 +304,14 @@ list(APPEND INKSCAPE_INCS_SYS ${FREETYPE_INCLUDE_DIRS}) list(APPEND INKSCAPE_LIBS ${FREETYPE_LIBRARIES}) -find_package(Boost REQUIRED) +if(WIN32) + find_package(Boost REQUIRED COMPONENTS filesystem system) + list(APPEND INKSCAPE_LIBS ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY}) +else() + find_package(Boost REQUIRED COMPONENTS filesystem thread system) + list(APPEND INKSCAPE_LIBS ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_SYSTEM_LIBRARY}) +endif() list(APPEND INKSCAPE_INCS_SYS ${Boost_INCLUDE_DIRS}) -# list(APPEND INKSCAPE_LIBS ${Boost_LIBRARIES}) find_package(ASPELL) if(ASPELL_FOUND) === modified file 'src/display/drawing-group.cpp' --- src/display/drawing-group.cpp 2014-12-16 16:45:03 +0000 +++ src/display/drawing-group.cpp 2017-01-08 14:33:13 +0000 @@ -89,9 +89,12 @@ return beststate; } +#include "SharedMutexGuard.h" + unsigned DrawingGroup::_renderItem(DrawingContext &dc, Geom::IntRect const &area, unsigned flags, DrawingItem *stop_at) { + //SharedMutexGuard _lock(this->lock); // not needed because DrawingItem::render() already locks - but is that always the case? if (stop_at == NULL) { // normal rendering for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) { === modified file 'src/display/drawing-item.cpp' --- src/display/drawing-item.cpp 2014-12-26 14:44:07 +0000 +++ src/display/drawing-item.cpp 2017-01-08 15:35:18 +0000 @@ -215,6 +215,7 @@ void DrawingItem::appendChild(DrawingItem *item) { + lock.lock(); item->_parent = this; assert(item->_child_type == CHILD_ORPHAN); item->_child_type = CHILD_NORMAL; @@ -227,11 +228,13 @@ // rely on the appended child being in the default non-updated state. // We set propagate to true, because the child might have descendants of its own. item->_markForUpdate(STATE_ALL, true); + lock.unlock(); } void DrawingItem::prependChild(DrawingItem *item) { + lock.lock(); item->_parent = this; assert(item->_child_type == CHILD_ORPHAN); item->_child_type = CHILD_NORMAL; @@ -239,12 +242,16 @@ // See appendChild for explanation item->_state = STATE_ALL; item->_markForUpdate(STATE_ALL, true); + lock.unlock(); } +#include "SharedMutexGuard.h" + /// Delete all regular children of this item (not mask or clip). void DrawingItem::clearChildren() { + SharedMutexGuard<> _lock(this->lock); if (_children.empty()) return; _markForRendering(); @@ -340,7 +347,8 @@ { static const char *cache_env = getenv("_INKSCAPE_DISABLE_CACHE"); if (cache_env) return; - + + SharedMutexGuard<> myLock(this->lock); if (_cached_persistent && !persistent) return; @@ -482,6 +490,7 @@ void DrawingItem::setZOrder(unsigned z) { + SharedMutexGuard<> _lock(this->lock); if (!_parent) return; ChildrenList::iterator it = _parent->_children.iterator_to(*this); @@ -524,6 +533,7 @@ void DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset) { + SharedMutexGuard<> myLock(this->lock); bool render_filters = _drawing.renderFilters(); bool outline = _drawing.outline(); @@ -678,6 +688,7 @@ unsigned DrawingItem::render(DrawingContext &dc, Geom::IntRect const &area, unsigned flags, DrawingItem *stop_at) { + SharedMutexGuard myLock(this->lock); // WARNING: can deadlock when calling recursive, child functions since bool outline = _drawing.outline(); bool render_filters = _drawing.renderFilters(); @@ -691,6 +702,7 @@ // TODO convert outline rendering to a separate virtual function if (outline) { + myLock.Unlock(); _renderOutline(dc, area, flags); return RENDER_OK; } @@ -708,7 +720,12 @@ // render from cache if possible if (_cached) { if (_cache) { + lock.unlock_shared(); // can't call upgrade or else deadlock + lock.lock(); _cache->prepare(); + lock.unlock(); + lock.lock_shared(); + set_cairo_blend_operator( dc, _mix_blend_mode ); _cache->paintFromCache(dc, carea); @@ -720,7 +737,13 @@ Geom::OptIntRect cl = _drawing.cacheLimit(); cl.intersectWith(_drawbox); if (cl) { + // locking not really necessary since it's atomic, just to please ThreadSanitizer + // without it, there will be a whole bunch of false positives + lock.unlock_shared(); // can't call upgrade or else deadlock + lock.lock(); _cache = new DrawingCache(*cl); + lock.unlock(); + lock.lock_shared(); } } } else { @@ -759,6 +782,7 @@ // filters and opacity do not apply when rendering the ancestors of the filtered // element if ((flags & RENDER_FILTER_BACKGROUND) || !needs_intermediate_rendering) { + myLock.Unlock(); return _renderItem(dc, *carea, flags & ~RENDER_FILTER_BACKGROUND, stop_at); } @@ -810,7 +834,10 @@ // 3. Render object itself ict.pushGroup(); + + lock.unlock_shared(); render_result = _renderItem(ict, *iarea, flags, stop_at); + lock.lock_shared(); // 4. Apply filter. if (_filter && render_filters) { @@ -835,7 +862,7 @@ // the internals of the filter need to use cairo_get_group_target() // instead of cairo_get_target(). } - + // 5. Render object inside the composited mask + clip ict.popGroupToSource(); ict.setOperator(CAIRO_OPERATOR_IN); @@ -843,12 +870,20 @@ // 6. Paint the completed rendering onto the base context (or into cache) if (_cached && _cache) { + // upgrade lock here because to because creating DrawingContext will mutate _cache (calls createRawContext) + lock.unlock_shared(); // can't call upgrade or else deadlock + lock.lock(); + { DrawingContext cachect(*_cache); cachect.rectangle(*carea); cachect.setOperator(CAIRO_OPERATOR_SOURCE); cachect.setSource(&intermediate); cachect.fill(); _cache->markClean(*carea); + // destructor needs to be called before unlocking + } + lock.unlock(); + lock.lock_shared(); } dc.rectangle(*carea); dc.setSource(&intermediate); === modified file 'src/display/drawing-item.h' --- src/display/drawing-item.h 2014-12-21 14:29:02 +0000 +++ src/display/drawing-item.h 2017-01-04 15:39:50 +0000 @@ -19,6 +19,7 @@ #include #include #include +#include namespace Glib { class ustring; @@ -226,7 +227,8 @@ unsigned _isolation : 1; unsigned _mix_blend_mode : 4; - + boost::shared_mutex lock; + friend class Drawing; }; === modified file 'src/display/drawing.cpp' --- src/display/drawing.cpp 2015-03-05 12:41:18 +0000 +++ src/display/drawing.cpp 2017-01-08 14:17:27 +0000 @@ -44,7 +44,7 @@ , _grayscale_colormatrix(std::vector (grayscale_value_matrix, grayscale_value_matrix + 20 )) , _canvasarena(arena) { - + omp_init_lock(&lock); } Drawing::~Drawing() @@ -200,6 +200,7 @@ void Drawing::_pickItemsForCaching() { + omp_set_lock(&lock); // we cache the objects with the highest score until the budget is exhausted _candidate_items.sort(std::greater()); size_t used = 0; @@ -224,6 +225,7 @@ for (std::set::iterator j = to_uncache.begin(); j != to_uncache.end(); ++j) { (*j)->setCached(false); } + omp_unset_lock(&lock); } } // end namespace Inkscape === modified file 'src/display/drawing.h' --- src/display/drawing.h 2014-10-08 02:22:03 +0000 +++ src/display/drawing.h 2017-01-08 14:16:41 +0000 @@ -12,6 +12,7 @@ #ifndef SEEN_INKSCAPE_DISPLAY_DRAWING_H #define SEEN_INKSCAPE_DISPLAY_DRAWING_H +#include #include <2geom/rect.h> #include #include @@ -102,7 +103,7 @@ Filters::FilterColorMatrix::ColorMatrixMatrix _grayscale_colormatrix; SPCanvasArena *_canvasarena; // may be NULL if this arena is not the screen // but used for export etc. - + omp_lock_t lock; friend class DrawingItem; }; === modified file 'src/display/nr-filter-turbulence.cpp' --- src/display/nr-filter-turbulence.cpp 2016-12-22 07:40:37 +0000 +++ src/display/nr-filter-turbulence.cpp 2017-01-05 11:07:16 +0000 @@ -331,6 +314,7 @@ , fTileX(1) //guessed , fTileY(1) //guessed { + omp_init_lock(&lock); } FilterPrimitive * FilterTurbulence::create() { @@ -427,6 +411,7 @@ set_cairo_surface_ci(out, (SPColorInterpolation)_style->color_interpolation_filters.computed ); } + omp_set_lock(&lock); if (!gen->ready()) { Geom::Point ta(fTileX, fTileY); Geom::Point tb(fTileX + fTileWidth, fTileY + fTileHeight); @@ -394,6 +379,7 @@ Geom::Point(XbaseFrequency, YbaseFrequency), stitchTiles, type == TURBULENCE_FRACTALNOISE, numOctaves); } + omp_unset_lock(&lock); Geom::Affine unit_trans = slot.get_units().get_matrix_primitiveunits2pb().inverse(); Geom::Rect slot_area = slot.get_slot_area(); === modified file 'src/display/nr-filter-turbulence.h' --- src/display/nr-filter-turbulence.h 2014-10-08 02:22:03 +0000 +++ src/display/nr-filter-turbulence.h 2017-01-03 06:50:59 +0000 @@ -22,7 +22,7 @@ */ #include <2geom/point.h> - +#include #include "display/nr-filter-primitive.h" #include "display/nr-filter-slot.h" #include "display/nr-filter-units.h" @@ -73,7 +73,7 @@ double fTileX; double fTileY; - + omp_lock_t lock; }; } /* namespace Filters */ === modified file 'src/display/nr-filter.cpp' --- src/display/nr-filter.cpp 2016-05-24 15:04:26 +0000 +++ src/display/nr-filter.cpp 2017-01-08 15:47:19 +0000 @@ -109,6 +109,7 @@ return 1; } Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + //YZ: setFilter/BlurQuality are race conditions that can be ignored item->drawing().setFilterQuality(prefs->getInt("/options/filterquality/value", 0)); item->drawing().setBlurQuality(prefs->getInt("/options/blurquality/value", 0)); FilterQuality const filterquality = (FilterQuality)item->drawing().filterQuality(); @@ -232,6 +233,7 @@ /* TODO: fetch somehow the object ex and em lengths */ // Update for em, ex, and % values + // YZ: unresolved race condition - need to make this private to each thread _region_x.update(12, 6, len_x); _region_y.update(12, 6, len_y); _region_width.update(12, 6, len_x); === modified file 'src/display/nr-style.cpp' --- src/display/nr-style.cpp 2015-10-28 13:40:01 +0000 +++ src/display/nr-style.cpp 2017-01-05 13:18:12 +0000 @@ -75,6 +75,7 @@ , font_size(0) { paint_order_layer[0] = PAINT_ORDER_NORMAL; + omp_init_lock(&lock); } NRStyle::~NRStyle() @@ -341,8 +342,23 @@ update(); } +class LockGuard +{ +public: + LockGuard(omp_lock_t &l) : lock(l) + { + omp_set_lock(&lock); + } + ~LockGuard() + { + omp_unset_lock(&lock); + } + omp_lock_t &lock; +}; + bool NRStyle::prepareFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern) { + LockGuard _lock(this->lock); // update fill pattern if (!fill_pattern) { switch (fill.type) { @@ -407,6 +423,7 @@ bool NRStyle::prepareStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern) { + LockGuard _lock(this->lock); if (!stroke_pattern) { switch (stroke.type) { case PAINT_SERVER: === modified file 'src/display/nr-style.h' --- src/display/nr-style.h 2015-10-28 13:40:01 +0000 +++ src/display/nr-style.h 2017-01-05 09:40:05 +0000 @@ -15,6 +15,7 @@ #include #include <2geom/rect.h> +#include #include "color.h" class SPPaintServer; @@ -122,6 +123,7 @@ float font_size; int text_direction; + omp_lock_t lock; }; #endif === modified file 'src/display/sodipodi-ctrl.cpp' --- src/display/sodipodi-ctrl.cpp 2016-12-03 16:11:45 +0000 +++ src/display/sodipodi-ctrl.cpp 2017-01-05 11:54:40 +0000 @@ -209,6 +209,7 @@ ctrl->pixbuf = NULL; ctrl->_point = Geom::Point(0,0); + omp_init_lock(&ctrl->lock); } static void sp_ctrl_destroy(SPCanvasItem *object) @@ -565,9 +566,11 @@ if ((!ctrl->filled) && (!ctrl->stroked)) return; // the control-image is rendered into ctrl->cache + omp_set_lock(&ctrl->lock); if (!ctrl->build) { sp_ctrl_build_cache (ctrl); } + omp_unset_lock(&ctrl->lock); int w = (ctrl->width * 2 + 1); int h = (ctrl->height * 2 + 1); === modified file 'src/display/sodipodi-ctrl.h' --- src/display/sodipodi-ctrl.h 2016-12-03 16:11:45 +0000 +++ src/display/sodipodi-ctrl.h 2017-01-03 09:01:27 +0000 @@ -7,6 +7,7 @@ * */ +#include #include #include "sp-canvas-item.h" #include "enums.h" @@ -55,6 +56,7 @@ void moveto(Geom::Point const p); Geom::Point _point; + omp_lock_t lock; }; struct SPCtrlClass : public SPCanvasItemClass{ === modified file 'src/display/sp-canvas.cpp' --- src/display/sp-canvas.cpp 2017-01-02 08:19:07 +0000 +++ src/display/sp-canvas.cpp 2017-01-08 13:50:16 +0000 @@ -20,7 +20,7 @@ #ifdef HAVE_CONFIG_H # include #endif - +#include #include #include #include @@ -124,7 +124,7 @@ SPCanvasItem item; std::list items; - + boost::shared_mutex lock; }; /** @@ -745,6 +745,7 @@ static void sp_canvas_group_init(SPCanvasGroup * group) { new (&group->items) std::list; + new (&group->lock) boost::shared_mutex; } void SPCanvasGroup::destroy(SPCanvasItem *object) @@ -769,6 +770,9 @@ void SPCanvasGroup::update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags) { SPCanvasGroup const *group = SP_CANVAS_GROUP(item); + // must not render when in update + group->lock.lock(); + Geom::OptRect bounds; for (std::list::const_iterator it = group->items.begin(); it != group->items.end(); ++it) { @@ -791,6 +795,7 @@ // FIXME ? item->x1 = item->x2 = item->y1 = item->y2 = 0; } + group->lock.unlock(); } double SPCanvasGroup::point(SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item) @@ -840,7 +845,9 @@ void SPCanvasGroup::render(SPCanvasItem *item, SPCanvasBuf *buf) { SPCanvasGroup const *group = SP_CANVAS_GROUP(item); - + // protect it from SPCanvasGroup::update(), called by idle_handler -> doUpdate + group->lock.lock_shared(); + for (std::list::const_iterator it = group->items.begin(); it != group->items.end(); ++it) { SPCanvasItem *child = *it; if (child->visible) { @@ -854,6 +861,7 @@ } } } + group->lock.unlock_shared(); } void SPCanvasGroup::viewboxChanged(SPCanvasItem *item, Geom::IntRect const &new_area) @@ -1541,10 +1549,12 @@ cairo_surface_destroy(imgs); // Mark the painted rectangle clean + #pragma omp critical // todo: make this lock instead of critical section + { markRect(paint_rect, 0); - gtk_widget_queue_draw_area(GTK_WIDGET(this), paint_rect.left() -_x0, paint_rect.top() - _y0, paint_rect.width(), paint_rect.height()); + } } struct PaintRectSetup { @@ -1594,6 +1604,37 @@ if ((bw < 1) || (bh < 1)) return 0; + #if 1 + // YZ - multithread + using namespace std::chrono; + { + int idealBlocks = omp_get_num_procs(); // could use more to improve load balancing, but the extra cost of iterating through the scene and additional per polygon setup will probably offset this + int blockSize = (this_rect.height() + idealBlocks - 1) / idealBlocks; //64 use small size to test for race conditions + // Always paint towards the mouse first + auto t0 = system_clock::now(); + bool timeOut = false; + #pragma omp parallel + { + #pragma omp for lastprivate(timeOut) + for (int y = this_rect.top(); y < this_rect.bottom(); y += blockSize) + { + unsigned timeElapsed = duration_cast(system_clock::now() - t0).count(); + if (0)//timeElapsed > 400) // don't time out if we want to measure rendering time + { + timeOut = true; + continue; // can't break out of parallel loop + } + Geom::IntRect r(this_rect.left(), y, this_rect.right(), std::min(y + blockSize, this_rect.bottom())); + paintSingleBuffer(r, setup->big_rect, bw); + } + #if 0 + #pragma omp critical + printf("t_thread%d=%llu\n", omp_get_thread_num(), duration_cast(system_clock::now() - t0).count()); + #endif + } + return !timeOut; + } + #endif if (bw * bh < setup->max_pixels) { // We are small enough /* @@ -1700,7 +1741,7 @@ if (_rendermode != Inkscape::RENDERMODE_OUTLINE) { // use 256K as a compromise to not slow down gradients // 256K is the cached buffer and we need 4 channels - setup.max_pixels = 65536; // 256K/4 + setup.max_pixels = 1 << 23; // Y.Z. - 65536 was too small - large blurs were very inefficient because they required large intermediate renderings that are bigger than the rendered region itself! } else { // paths only, so 1M works faster // 1M is the cached buffer and we need 4 channels @@ -1798,8 +1839,12 @@ } } +bool renderLoop; int SPCanvas::paint() { + using namespace std::chrono; +start: + auto t0 = system_clock::now(); if (_need_update) { sp_canvas_item_invoke_update(_root, Geom::identity(), 0); _need_update = FALSE; @@ -1817,6 +1862,7 @@ cairo_region_get_rectangle(to_draw, i, &crect); if (!paintRect(crect.x, crect.y, crect.x + crect.width, crect.y + crect.height)) { // Aborted + printf("t_paint2=%llu\n", duration_cast(system_clock::now() - t0).count()); return FALSE; }; } @@ -1825,6 +1871,12 @@ if (_forced_redraw_limit != -1) { _forced_redraw_count = 0; } + printf("t_paint=%llu\n", duration_cast(system_clock::now() - t0).count()); + if (renderLoop) + { + dirtyRect(Geom::IntRect(_x0, _y0, _x0 + allocation.width, _y0 + allocation.height)); + goto start; + } return TRUE; } === modified file 'src/sp-item.cpp' --- src/sp-item.cpp 2016-11-02 23:08:41 +0000 +++ src/sp-item.cpp 2017-01-01 07:52:15 +0000 @@ -94,6 +94,7 @@ style->signal_stroke_ps_changed.connect(sigc::bind(sigc::ptr_fun(stroke_ps_ref_changed), this)); avoidRef = new SPAvoidRef(this); + omp_init_lock(&lock); } SPItem::~SPItem() { @@ -1172,6 +1173,7 @@ void SPItem::invoke_hide(unsigned key) { + omp_set_lock(&lock); this->hide(key); SPItemView *ref = NULL; @@ -1207,6 +1209,7 @@ } v = next; } + omp_unset_lock(&lock); } // Adjusters === modified file 'src/sp-item.h' --- src/sp-item.h 2016-11-02 23:08:41 +0000 +++ src/sp-item.h 2017-01-01 07:51:05 +0000 @@ -31,6 +31,7 @@ //class SPGuideConstraint; #include "sp-guide-constraint.h" +#include class SPClipPathReference; class SPMaskReference; @@ -401,6 +402,7 @@ virtual void convert_to_guides() const; virtual int event(SPEvent *event); + omp_lock_t lock; }; === modified file 'src/style.h' --- src/style.h 2016-10-19 11:11:05 +0000 +++ src/style.h 2017-01-04 08:03:56 +0000 @@ -58,8 +58,8 @@ void mergeString( char const *const p ); bool operator==(const SPStyle& rhs); - int style_ref() { ++_refcount; return _refcount; } - int style_unref() { --_refcount; return _refcount; } + int style_ref() { __sync_fetch_and_add(&_refcount, 1); return _refcount; } // Y.Z. is this needed any more after fixing other more fundamental race conditions? + int style_unref() { __sync_fetch_and_add(&_refcount, -1); return _refcount; } int refCount() { return _refcount; } private: === new file 'src/display/SharedMutexGuard.h' +++ src/display/SharedMutexGuard.h 2017-01-08 06:32:21.535581826 -0800 @@ -0,0 +1,29 @@ +template +class SharedMutexGuard +{ +public: + SharedMutexGuard(boost::shared_mutex &l) : lock(l) + { + if (shared) + lock.lock_shared(); + else + lock.lock(); + locked = true; + } + ~SharedMutexGuard() + { + Unlock(); + } + void Unlock() + { + if (!locked) + return; + if (shared) + lock.unlock_shared(); + else + lock.unlock(); + locked = false; + } + bool locked; + boost::shared_mutex &lock; +};