From e2a2b93da5e6fa0eb3394c3bbd8be7d0ee280810 Mon Sep 17 00:00:00 2001 From: Jabier Arraiza Date: Sat, 6 Feb 2021 14:42:48 +0100 Subject: [PATCH] failed MR :( --- src/display/control/canvas-item-drawing.cpp | 4 + src/display/drawing-item.cpp | 73 +++++++-- src/display/drawing-item.h | 3 + src/display/drawing-surface.cpp | 33 ++++- src/display/drawing-surface.h | 1 + src/display/drawing.cpp | 155 +++++++++++++++++++- src/display/drawing.h | 10 +- src/object/sp-item.cpp | 2 +- src/ui/dialog/inkscape-preferences.cpp | 8 +- src/ui/dialog/inkscape-preferences.h | 1 + src/ui/widget/canvas.cpp | 7 + src/ui/widget/canvas.h | 3 +- 12 files changed, 277 insertions(+), 23 deletions(-) diff --git a/src/display/control/canvas-item-drawing.cpp b/src/display/control/canvas-item-drawing.cpp index 17750b726d..d28c7bf441 100644 --- a/src/display/control/canvas-item-drawing.cpp +++ b/src/display/control/canvas-item-drawing.cpp @@ -191,6 +191,10 @@ void CanvasItemDrawing::render(Inkscape::CanvasItemBuffer *buf) Inkscape::DrawingContext dc(buf->cr->cobj(), buf->rect.min()); _drawing->update(); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/options/threading/precaching", false)) { + _drawing->prerender(buf->rect); + } _drawing->render(dc, buf->rect); } diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index 2c6848f6c2..9fe38a8438 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -16,7 +16,6 @@ #include "display/drawing-group.h" #include "display/drawing-item.h" #include "display/drawing-pattern.h" -#include "display/drawing-surface.h" #include "display/drawing-text.h" #include "display/drawing.h" @@ -718,7 +717,7 @@ DrawingItem::render(DrawingContext &dc, Geom::IntRect const &area, unsigned flag if (!iarea) { return RENDER_OK; } - + // Device scale for HiDPI screens (typically 1 or 2) int device_scale = dc.surface()->device_scale(); @@ -788,10 +787,64 @@ DrawingItem::render(DrawingContext &dc, Geom::IntRect const &area, unsigned flag dc.setOperator(ink_css_blend_to_cairo_operator(SP_CSS_BLEND_NORMAL)); return _renderItem(dc, *iarea, flags & ~RENDER_FILTER_BACKGROUND, stop_at); } + + unsigned render_result = RENDER_OK; + DrawingSurface intermediate(*iarea, device_scale); + render_result = renderItem(intermediate, carea, iarea, device_scale, flags, stop_at); + dc.rectangle(*carea); + dc.setSource(&intermediate); + // 7. Render blend mode + dc.setOperator(ink_css_blend_to_cairo_operator(_mix_blend_mode)); + dc.fill(); + dc.setSource(0,0,0,0); + // Web isolation only works if parent doesnt have transform - DrawingSurface intermediate(*iarea, device_scale); + // the call above is to clear a ref on the intermediate surface held by dc + + return render_result; +} + +void +DrawingItem::prerender(Geom::OptIntRect const &area) +{ + // we check correcness of item previously on OMP launch + // carea is the area to paint + if (!area) { + return; + } + Geom::OptIntRect carea = Geom::intersect(area,_cacheRect()); + if (!carea) { + return; + } + + // Device scale for HiDPI screens (typically 1 or 2) + int device_scale = drawing().getCanvasItemDrawing()->get_canvas()->get_scale_factor(); + + static int counter = 0; + if (!_cache) { + + // There is no cache. This could be because caching of this item + // was just turned on after the last update phase, or because + // we were previously outside of the canvas. + _cache = new DrawingCache(*carea, device_scale); + } else { + _cache->prepare(); + } + _cache->getPaintAreaCache(carea, false); + _cached_persistent = true; + if (carea) { + unsigned render_result = RENDER_OK; + DrawingSurface intermediate(*carea, device_scale); + render_result = renderItem(intermediate,carea, carea, device_scale, 0, nullptr); + } +} + +unsigned +DrawingItem::renderItem(DrawingSurface &intermediate, Geom::OptIntRect const &carea, Geom::OptIntRect const &iarea, int device_scale, unsigned flags, DrawingItem *stop_at) +{ DrawingContext ict(intermediate); + bool render_filters = _drawing.renderFilters(); // This path fails for patterns/hatches when stepping the pattern to handle overflows. // The offsets are applied to drawing context (dc) but they are not copied to the @@ -884,21 +937,11 @@ DrawingItem::render(DrawingContext &dc, Geom::IntRect const &area, unsigned flag _cache->markClean(*iarea); } } - - dc.rectangle(*carea); - dc.setSource(&intermediate); - // 7. Render blend mode - dc.setOperator(ink_css_blend_to_cairo_operator(_mix_blend_mode)); - dc.fill(); - dc.setSource(0,0,0,0); - // Web isolation only works if parent doesnt have transform - - - // the call above is to clear a ref on the intermediate surface held by dc - + return render_result; } + void DrawingItem::_renderOutline(DrawingContext &dc, Geom::IntRect const &area, unsigned flags) { diff --git a/src/display/drawing-item.h b/src/display/drawing-item.h index b1f88a017e..6814eeca73 100644 --- a/src/display/drawing-item.h +++ b/src/display/drawing-item.h @@ -18,6 +18,7 @@ #include #include #include +#include "display/drawing-surface.h" #include #include @@ -139,6 +140,8 @@ public: void update(Geom::IntRect const &area = Geom::IntRect::infinite(), UpdateContext const &ctx = UpdateContext(), unsigned flags = STATE_ALL, unsigned reset = 0); unsigned render(DrawingContext &dc, Geom::IntRect const &area, unsigned flags = 0, DrawingItem *stop_at = nullptr); + unsigned renderItem(DrawingSurface &intermediate, Geom::OptIntRect const &carea, Geom::OptIntRect const &iarea, int device_scale, unsigned flags, DrawingItem *stop_at); + void prerender(Geom::OptIntRect const &area); void clip(DrawingContext &dc, Geom::IntRect const &area); DrawingItem *pick(Geom::Point const &p, double delta, unsigned flags = 0); diff --git a/src/display/drawing-surface.cpp b/src/display/drawing-surface.cpp index c84e358f86..6879377c90 100644 --- a/src/display/drawing-surface.cpp +++ b/src/display/drawing-surface.cpp @@ -220,7 +220,7 @@ void DrawingCache::markClean(Geom::IntRect const &area) { Geom::OptIntRect r = Geom::intersect(area, pixelArea()); - if (!r) return; + if (!r || !_clean_region) return; cairo_rectangle_int_t clean = _convertRect(*r); cairo_region_union_rectangle(_clean_region, &clean); } @@ -338,6 +338,37 @@ void DrawingCache::paintFromCache(DrawingContext &dc, Geom::OptIntRect &area, bo cairo_region_destroy(cache_region); } +/** + * Modifies the @a area + * parameter to the bounds of the region that must be repainted. + */ +void DrawingCache::getPaintAreaCache(Geom::OptIntRect &area, bool is_filter) +{ + if (!area) return; + + // We subtract the clean region from the area, then get the bounds + // of the resulting region. This is the area that needs to be repainted + // by the item. + // Then we subtract the area that needs to be repainted from the + // original area and paint the resulting region from cache. + cairo_rectangle_int_t area_c = _convertRect(*area); + cairo_region_t *dirty_region = cairo_region_create_rectangle(&area_c); + cairo_region_subtract(dirty_region, _clean_region); + + if (is_filter && !cairo_region_is_empty(dirty_region)) { // To allow fast panning on high zoom on filters + return; + } + if (cairo_region_is_empty(dirty_region)) { + area = Geom::OptIntRect(); + } else { + cairo_rectangle_int_t to_repaint; + cairo_region_get_extents(dirty_region, &to_repaint); + area = _convertRect(to_repaint); + markDirty(*area); + } + cairo_region_destroy(dirty_region); +} + // debugging utility void DrawingCache::_dumpCache(Geom::OptIntRect const &area) diff --git a/src/display/drawing-surface.h b/src/display/drawing-surface.h index 0522b53e61..014f22a17b 100644 --- a/src/display/drawing-surface.h +++ b/src/display/drawing-surface.h @@ -73,6 +73,7 @@ public: void scheduleTransform(Geom::IntRect const &new_area, Geom::Affine const &trans); void prepare(); void paintFromCache(DrawingContext &dc, Geom::OptIntRect &area, bool is_filter); + void getPaintAreaCache(Geom::OptIntRect &area, bool is_filter); protected: cairo_region_t *_clean_region; diff --git a/src/display/drawing.cpp b/src/display/drawing.cpp index e143fa9d77..f521abcc49 100644 --- a/src/display/drawing.cpp +++ b/src/display/drawing.cpp @@ -22,6 +22,9 @@ #include "cairo-templates.h" #include "drawing-context.h" +// yeld_worker class +#include +#include namespace Inkscape { @@ -33,18 +36,114 @@ static const gdouble grayscale_value_matrix[20] = { 0 , 0 , 0 , 1, 0 }; +// https://lemire.me/blog/2020/06/10/reusing-a-thread-in-c-for-better-performance/ +// https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/tree/master/2020/06/10 +class yield_worker { +public: + yield_worker() = default; + inline ~yield_worker() { + stop_threads(); + } + inline void stop_threads() { + for (int i = 0; i < num_cpus; i++) { + has_works.store(0); + } + exitings.store(true); + for (int i = 0; i < num_cpus; i++) { + if (threads[i].joinable()) { + threads[i].join(); + } + } + } + + inline void finish() { + while (has_works.load() > 0) { + } + } + inline void work(std::vector ditems) { + int amount = ditems.size()/num_cpus; + has_works.store(-1); + for (int i = 0; i < num_cpus; i++) { + std::vector work; + for (size_t j = 0;j < (amount) + 1;j++) { + size_t current = (i * (j + 1)) + j; + if (current < ditems.size()) { + work.push_back(ditems[current]); + } + } + work_on.push_back(work); + } + has_works.store(num_cpus); + if (!is_started.load()) { + run(); + } + } + + + inline unsigned run() { + threads.reserve(num_cpus); + work_on.reserve(num_cpus); + for (unsigned i = 0; i < num_cpus; ++i) { + is_started.store(true); + threads.push_back(std::thread([this, i] { + while (true) { + while (has_works.load() < 1 || !num_cpus) { + if (exitings.load()) { + return 0; + } + std::this_thread::yield(); + } + int remaining = has_works.load(); + if (remaining > 0) { + if (!work_on[i].empty()) { + for(auto ditem:work_on[i]) { + ditem->prerender(area); + } + } + has_works.store(remaining - 1); + } + } + })); + } + return 0; + } + + unsigned num_cpus = 0; + Geom::OptIntRect area = Geom::OptIntRect(); + std::atomic is_started{false}; + std::atomic has_works{0}; +private: + std::vector threads; + std::vector< std::vector > work_on; + std::atomic exitings{false}; +}; + Drawing::Drawing(Inkscape::CanvasItemDrawing *canvas_item_drawing) : _canvas_item_drawing(canvas_item_drawing) , _grayscale_colormatrix(std::vector(grayscale_value_matrix, grayscale_value_matrix + 20)) { + yw = new yield_worker(); // _canvas_item_drawing can be null. Used this way by Eraser tool. } Drawing::~Drawing() { + if (yw->is_started.load()) { + yw->stop_threads(); + delete yw; + yw = nullptr; + } delete _root; } +void +Drawing::resetYW() +{ + if (yw) { + yw->has_works.store(-1); + } +} + void Drawing::setRoot(DrawingItem *item) { @@ -201,6 +300,61 @@ Drawing::render(DrawingContext &dc, Geom::IntRect const &area, unsigned flags, i } } +void +Drawing::prerender(Geom::IntRect const &area) +{ + std::vector ditems; + for (auto ditem: _cached_items) { + // If we are invisible, return immediately + DrawingImage * di = dynamic_cast(ditem); + DrawingShape * ds = dynamic_cast(ditem); + DrawingText * dt = dynamic_cast (ditem); + if (!dt && !di && !ds) { + continue; + } + if (!ditem->_visible) { + continue; + } + if (ditem->_ctm.isSingular(1e-18)) { + continue; + } + // TODO convert outline rendering to a separate virtual function + if (outline()) { + continue; + } + // carea is the area to paint + Geom::OptIntRect carea = Geom::intersect(area, ditem->_drawbox); + if (!carea) { + continue; + } + + if (!ditem->_cached) { + continue; + } + + if (renderFilters() && ditem->_filter) { + continue; + } + + ditems.push_back(ditem); + } + if (!num_cpus) { + num_cpus = std::thread::hardware_concurrency(); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int prefsthreadnum = prefs->getIntLimited("/options/threading/numthreads", num_cpus, 1, 256); + if (prefsthreadnum < num_cpus) { + num_cpus = prefsthreadnum; + } + } + + if (ditems.size() && yw->has_works.load() == 0) { + yw->area = area; + yw->num_cpus = num_cpus; + yw->work(ditems); // issue the work + } + ditems.clear(); +} + DrawingItem * Drawing::pick(Geom::Point const &p, double delta, unsigned flags) { @@ -254,7 +408,6 @@ Drawing::average_color(Geom::IntRect const &area, double &R, double &G, double & ink_cairo_surface_average_color_premul(surface->cobj(), R, G, B, A); } - } // end namespace Inkscape /* diff --git a/src/display/drawing.h b/src/display/drawing.h index 3319fac7f5..e47b37ae19 100644 --- a/src/display/drawing.h +++ b/src/display/drawing.h @@ -20,6 +20,9 @@ #include #include "display/drawing-item.h" +#include "display/drawing-text.h" +#include "display/drawing-shape.h" +#include "display/drawing-image.h" #include "display/rendermode.h" #include "nr-filter-gaussian.h" // BLUR_QUALITY_BEST #include "nr-filter-colormatrix.h" @@ -30,6 +33,7 @@ namespace Inkscape { class DrawingItem; class CanvasItemDrawing; +class yield_worker; class Drawing : boost::noncopyable @@ -78,8 +82,9 @@ public: unsigned reset = 0); void render(DrawingContext &dc, Geom::IntRect const &area, unsigned flags = 0, int antialiasing = -1); + void prerender(Geom::IntRect const &area); DrawingItem *pick(Geom::Point const &p, double delta, unsigned flags); - + void resetYW(); void average_color(Geom::IntRect const &area, double &R, double &G, double &B, double &A); sigc::signal signal_request_update; @@ -88,7 +93,8 @@ public: private: void _pickItemsForCaching(); - + Inkscape::yield_worker *yw; + int num_cpus = 0; typedef std::list CandidateList; bool _outline_sensitive = false; DrawingItem *_root = nullptr; diff --git a/src/object/sp-item.cpp b/src/object/sp-item.cpp index 19e7f0e878..bf57f7a923 100644 --- a/src/object/sp-item.cpp +++ b/src/object/sp-item.cpp @@ -1696,7 +1696,7 @@ sp_item_view_list_remove(SPItemView *list, SPItemView *view) while (prev->next != view) prev = prev->next; prev->next = view->next; } - + view->arenaitem->drawing().resetYW(); delete view->arenaitem; g_free(view); diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp index f62a89b181..90841159f8 100644 --- a/src/ui/dialog/inkscape-preferences.cpp +++ b/src/ui/dialog/inkscape-preferences.cpp @@ -2501,9 +2501,13 @@ void InkscapePreferences::initPageRendering() _filter_multi_threaded.init("/options/threading/numthreads", 1.0, 8.0, 1.0, 2.0, 4.0, true, false); _page_rendering.add_line( false, _("Number of _Threads:"), _filter_multi_threaded, _("(requires restart)"), _("Configure number of processors/threads to use when rendering filters"), false); - + + /* threaded precaching*/ //related comments/widgets/functions should be renamed and option should be moved elsewhere when inkscape is fully multi-threaded + _cache_multi_threaded.init ( _("Experimental thread precache"), "/options/threading/precaching", false); + _page_rendering.add_line( false, "", _cache_multi_threaded, "", + _("Esperimental threading based precaching not filtered elements")); // rendering cache - _rendering_cache_size.init("/options/renderingcache/size", 0.0, 4096.0, 1.0, 32.0, 64.0, true, false); + _rendering_cache_size.init("/options/renderingcache/size", 0.0, 32768.0, 1.0, 32.0, 512.0, true, false); _page_rendering.add_line( false, _("Rendering _cache size:"), _rendering_cache_size, C_("mebibyte (2^20 bytes) abbreviation","MiB"), _("Set the amount of memory per document which can be used to store rendered parts of the drawing for later reuse; set to zero to disable caching"), false); // rendering tile multiplier diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h index 4455a287be..26db415d06 100644 --- a/src/ui/dialog/inkscape-preferences.h +++ b/src/ui/dialog/inkscape-preferences.h @@ -330,6 +330,7 @@ protected: UI::Widget::PrefSpinButton _rendering_outline_overlay_opacity; UI::Widget::PrefCombo _rendering_redraw_priority; UI::Widget::PrefSpinButton _filter_multi_threaded; + UI::Widget::PrefCheckButton _cache_multi_threaded; UI::Widget::PrefCheckButton _trans_scale_stroke; UI::Widget::PrefCheckButton _trans_scale_corner; diff --git a/src/ui/widget/canvas.cpp b/src/ui/widget/canvas.cpp index a9acea927e..b789f1e145 100644 --- a/src/ui/widget/canvas.cpp +++ b/src/ui/widget/canvas.cpp @@ -935,6 +935,7 @@ Canvas::add_idle() } if (get_realized() && !_idle_connection.connected()) { + start = std::chrono::system_clock::now(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); guint redrawPriority = prefs->getIntLimited("/options/redrawpriority/value", G_PRIORITY_HIGH_IDLE, G_PRIORITY_HIGH_IDLE, G_PRIORITY_DEFAULT_IDLE); if (_in_full_redraw) { @@ -975,8 +976,14 @@ Canvas::on_idle() if (n_rects > 1) { done = false; + } else if (done) { + std::chrono::time_point end = std::chrono::system_clock::now(); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + const gchar *rendering = prefs->getBool("/options/threading/precaching", false) ? "multithread::" : "normal::"; + std::cout << "Render time " << rendering << ((std::chrono::duration_cast(end - start).count()) /1000.0) << " ms " << std::endl; } + return !done; } diff --git a/src/ui/widget/canvas.h b/src/ui/widget/canvas.h index a902088909..42c4b77960 100644 --- a/src/ui/widget/canvas.h +++ b/src/ui/widget/canvas.h @@ -23,6 +23,7 @@ #include "preferences.h" // Update canvas_item_ctrl sizes. #include "display/rendermode.h" +#include class SPDesktop; @@ -199,7 +200,7 @@ private: bool _all_enter_events = false; ///< Keep all enter events. Only set true in connector-tool.cpp. bool _is_dragging = false; ///< Used in selection-chemistry to block undo/redo. int _state = 0; ///< Last know modifier state (SHIFT, CTRL, etc.). - + std::chrono::time_point start = std::chrono::system_clock::now(); Inkscape::CanvasItem *_current_canvas_item = nullptr; ///< Item containing cursor, nullptr if none. Inkscape::CanvasItem *_current_canvas_item_new = nullptr; ///< Item to become _current_item, nullptr if none. Inkscape::CanvasItem *_grabbed_canvas_item = nullptr; ///< Item that holds a pointer grab; nullptr if none. -- GitLab