klayout/src/laybasic/layRedrawThread.cc

434 lines
13 KiB
C++
Raw Normal View History

/*
KLayout Layout Viewer
Copyright (C) 2006-2016 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <QEvent>
#include <QApplication>
#include "layRedrawThread.h"
#include "layRedrawThreadWorker.h"
#include "tlLog.h"
#include "tlAssert.h"
#include "dbHershey.h"
#include "dbShape.h"
#include <memory>
namespace lay
{
// -------------------------------------------------------------
// RedrawThread implementation
RedrawThread::RedrawThread (lay::RedrawThreadCanvas *canvas, lay::LayoutView *view)
: tl::Object ()
{
m_initial_update = false;
mp_canvas = canvas;
mp_view = view;
m_start_recursion_sentinel = false;
m_width = 0;
m_height = 0;
m_resolution = 1.0;
m_boxes_already_drawn = false;
m_custom_already_drawn = false;
m_nlayers = 0;
m_clock = tl::Clock::current ();
}
RedrawThread::~RedrawThread ()
{
// .. nothing yet ..
}
void RedrawThread::layout_changed ()
{
if (is_running () && tl::verbosity () >= 30) {
tl::info << tl::to_string (QObject::tr ("Layout changed: redraw thread stopped"));
}
// if something changed on the layouts we observe, stop the redraw thread
stop ();
}
void
RedrawThread::task_finished (int task_id)
{
// mark this entry as already drawn.
// HINT: that is MT safe given the exclusive nature of the tasks ...
if (task_id == draw_custom_queue_entry) {
m_custom_already_drawn = true;
} else if (task_id == draw_boxes_queue_entry) {
m_boxes_already_drawn = true;
} else if (task_id >= 0 && task_id < int (m_layers.size ())) {
m_layers [task_id].enabled = false;
}
}
std::vector<db::DBox>
subtract_box (const db::DBox &subject, const db::DBox &with)
{
std::vector<db::DBox> res;
std::vector<db::DBox> inverted;
double lim = std::numeric_limits<double>::max () * 0.5 /*safety*/;
inverted.reserve (4);
inverted.push_back (db::DBox (db::DPoint (-lim, -lim), db::DPoint (lim, with.bottom ())));
inverted.push_back (db::DBox (db::DPoint (-lim, with.top ()), db::DPoint (lim, lim)));
inverted.push_back (db::DBox (db::DPoint (-lim, with.bottom ()), db::DPoint (with.left (), with.top ())));
inverted.push_back (db::DBox (db::DPoint (with.right (), with.bottom ()), db::DPoint (lim, with.top ())));
for (std::vector<db::DBox>::const_iterator i = inverted.begin (); i != inverted.end (); ++i) {
db::DBox part = subject & *i;
if (! part.empty ()) {
res.push_back (part);
}
}
return res;
}
void
RedrawThread::commit (const std::vector <lay::RedrawLayerInfo> &layers, const lay::Viewport &vp, double resolution)
{
m_vp_trans = vp.trans ();
m_width = vp.width ();
m_height = vp.height ();
m_resolution = resolution;
m_layers = layers;
m_nlayers = m_layers.size ();
for (size_t i = 0; i < m_layers.size (); ++i) {
if (m_layers [i].visible) {
m_layers [i].enabled = false;
}
}
db::DBox new_region = m_vp_trans.inverted () * db::DBox (db::DPoint (0, 0), db::DPoint (m_width, m_height));
m_last_center = new_region.center ();
m_stored_region = m_valid_region = new_region;
m_stored_fp = m_vp_trans.fp_trans ();
m_boxes_already_drawn = false;
m_custom_already_drawn = false;
}
void
RedrawThread::start (int workers, const std::vector <lay::RedrawLayerInfo> &layers, const lay::Viewport &vp, double resolution, bool force_redraw)
{
m_vp_trans = vp.trans ();
m_width = vp.width ();
m_height = vp.height ();
m_resolution = resolution;
db::DBox new_region = m_vp_trans.inverted () * db::DBox (db::DPoint (0, 0), db::DPoint (m_width, m_height));
double epsilon = m_vp_trans.inverted ().ctrans (1e-3);
db::Vector sv;
db::Vector *shift_vector = 0;
// test, if we can shift the current image and redraw only the missing parts
if (! force_redraw && mp_canvas->shift_supported () &&
m_valid_region.overlaps (new_region) && m_stored_fp == m_vp_trans.fp_trans () &&
fabs (new_region.width () - m_stored_region.width ()) < epsilon && fabs (new_region.height () - m_stored_region.height ()) < epsilon) {
db::Box full (db::Point (0, 0), db::Point (m_width, m_height));
// yes we can ...
std::vector<db::DBox> parts = subtract_box (new_region, m_valid_region);
m_redraw_regions.clear ();
m_redraw_regions.reserve (parts.size ());
for (std::vector<db::DBox>::const_iterator p = parts.begin (); p != parts.end (); ++p) {
if (p->width () > epsilon && p->height () > epsilon) {
db::Box rr = db::Box ((m_vp_trans * *p).enlarged (db::DVector (1.0, 1.0) /*safety overlap*/));
rr &= full;
if (! rr.empty ()) {
m_redraw_regions.push_back (rr);
}
}
}
sv = db::Vector (m_vp_trans * db::DVector (m_last_center - new_region.center ()));
shift_vector = &sv;
} else {
// no, we can't ...
m_redraw_regions.clear ();
m_redraw_regions.push_back (db::Box (db::Point (0, 0), db::Point (m_width, m_height)));
// mark current image as unusable
m_valid_region = m_stored_region = db::DBox ();
}
m_last_center = new_region.center ();
std::vector<int> restart;
do_start (true, shift_vector, &layers, restart, workers);
}
void
RedrawThread::restart (const std::vector<int> &restart)
{
m_redraw_regions.clear ();
m_redraw_regions.push_back (db::Box (db::Point (0, 0), db::Point (m_width, m_height)));
do_start (false, 0, 0, restart, -1);
}
void
RedrawThread::change_visibility (const std::vector<bool> &visibility)
{
for (unsigned int i = 0; i < visibility.size () && i < m_layers.size (); ++i) {
m_layers [i].visible = visibility [i];
}
}
void
RedrawThread::do_start (bool clear, const db::Vector *shift_vector, const std::vector <lay::RedrawLayerInfo> *layers, const std::vector<int> &restart, int nworkers)
{
// change the number of workers if required.
if (nworkers >= 0 && nworkers != num_workers ()) {
set_num_workers (nworkers);
}
m_initial_update = true;
// We need this recursion sentinel because start may be called recursively
// via Layout::update which may open a progress widget and do processEvents.
// This may result in a repaint for example issuing a "start" call.
// Recursion however is forbidden because of the deadlocking locks used originally
// to lock out the redraw thread.
if (m_start_recursion_sentinel) {
return;
}
m_start_recursion_sentinel = true;
{
if (tl::verbosity () >= 40) {
tl::info << tl::to_string (QObject::tr ("Preparing to draw"));
}
tl::SelfTimer timer (tl::verbosity () >= 41, tl::to_string (QObject::tr ("Preparing to draw")));
// detach from all layout objects
tl::Object::detach_from_all_events ();
// Update all relevant layout objects.
for (unsigned int i = 0; i < mp_view->cellviews (); ++i) {
const lay::CellView &cv = mp_view->cellview (i);
if (cv.is_valid () && ! cv->layout ().under_construction () && ! (cv->layout ().manager () && cv->layout ().manager ()->transacting ())) {
cv->layout ().update ();
// attach to the layout object to receive change notifications to stop the redraw thread
cv->layout ().hier_changed_event.add (this, &RedrawThread::layout_changed);
cv->layout ().bboxes_changed_event.add (this, &RedrawThread::layout_changed);
}
}
mp_view->annotation_shapes ().update ();
// attach to the layout object to receive change notifications to stop the redraw thread
mp_view->annotation_shapes ().hier_changed_event.add (this, &RedrawThread::layout_changed); // not really required, since the shapes have no hierarchy, but for completeness ..
mp_view->annotation_shapes ().bboxes_changed_event.add (this, &RedrawThread::layout_changed);
mp_view->cellviews_about_to_change_event.add (this, &RedrawThread::layout_changed);
mp_view->cellview_about_to_change_event.add (this, &RedrawThread::layout_changed_with_int);
m_initial_update = true;
if (clear) {
m_layers = *layers;
}
m_nlayers = m_layers.size ();
if (mp_view->cellviews () > 0) {
if (clear) {
mp_canvas->prepare (m_nlayers * planes_per_layer + special_planes_before + special_planes_after, m_width, m_height, m_resolution, shift_vector, 0, mp_view->drawings ());
m_boxes_already_drawn = false;
m_custom_already_drawn = false;
} else {
// determine the planes to initialize
std::vector<int> planes_to_init;
for (std::vector<int>::const_iterator l = restart.begin (); l != restart.end (); ++l) {
if (*l == draw_custom_queue_entry) {
planes_to_init.push_back (-1);
} else if (*l >= 0 && *l < int (m_layers.size ())) {
for (int o = 0; o < planes_per_layer; ++o) {
planes_to_init.push_back (o + *l * planes_per_layer + special_planes_before + special_planes_after);
}
}
}
mp_canvas->prepare (m_nlayers * planes_per_layer + special_planes_before + special_planes_after, m_width, m_height, m_resolution, shift_vector, &planes_to_init, mp_view->drawings ());
for (std::vector<int>::const_iterator l = restart.begin (); l != restart.end (); ++l) {
if (*l >= 0 && *l < int (m_layers.size ())) {
m_layers [*l].enabled = true;
} else if (*l == draw_boxes_queue_entry) {
m_boxes_already_drawn = false;
} else if (*l == draw_custom_queue_entry) {
m_custom_already_drawn = false;
}
}
}
// set up the drawing tasks: controls the sequence of things to
// draw. First there are the visible layers, then there are the
// cell boundaries. Then come the invisible layers.
// decoration drawing
if (! m_custom_already_drawn) {
schedule (new RedrawThreadTask (draw_custom_queue_entry));
}
for (int i = 0; i < m_nlayers; ++i) {
if (m_layers [i].visible && m_layers [i].enabled) {
schedule (new RedrawThreadTask (i));
}
}
// cell box drawing
if (! m_boxes_already_drawn) {
schedule (new RedrawThreadTask (draw_boxes_queue_entry));
}
} else {
mp_canvas->prepare (1, m_width, m_height, m_resolution, 0, 0, mp_view->drawings ());
}
}
if (tl::verbosity () >= 21) {
m_main_timer.reset (new tl::SelfTimer ("Redrawing"));
}
start ();
m_initial_wait_lock.lock ();
// Don't wait on restart - that happens while a drawing is under way which was interrupted.
// Waiting is not necessary in this case and blocks the application.
if (m_initial_update && clear) {
m_initial_wait_cond.wait (&m_initial_wait_lock);
}
m_initial_update = false;
m_initial_wait_lock.unlock ();
m_start_recursion_sentinel = false;
}
void
RedrawThread::start ()
{
JobBase::start ();
}
tl::Worker *
RedrawThread::create_worker ()
{
return new RedrawThreadWorker (this);
}
void
RedrawThread::setup_worker (tl::Worker *worker)
{
RedrawThreadWorker *redraw_thread_worker = dynamic_cast<RedrawThreadWorker *> (worker);
if (redraw_thread_worker) {
redraw_thread_worker->setup (mp_view, mp_canvas, m_redraw_regions, m_vp_trans);
}
}
void
RedrawThread::stopped ()
{
// because we may have already shifted, we can only reuse the part that was inside the new viewport
m_stored_region = m_vp_trans.inverted () * db::DBox (db::DPoint (0, 0), db::DPoint (m_width, m_height));
m_valid_region &= m_stored_region;
m_stored_fp = m_vp_trans.fp_trans ();
done ();
}
void
RedrawThread::done ()
{
// stop timer if there is one
m_main_timer.reset (0);
wakeup ();
// release the workers' resources
for (int i = 0; i < num_workers (); ++i) {
RedrawThreadWorker *w = dynamic_cast <RedrawThreadWorker *> (worker (i));
if (w) {
w->finish ();
}
}
// send a signal to the canvas that the drawing has finished
mp_canvas->signal_end_of_drawing ();
}
void
RedrawThread::finished ()
{
m_stored_region = m_valid_region = m_vp_trans.inverted () * db::DBox (db::DPoint (0, 0), db::DPoint (m_width, m_height));
m_stored_fp = m_vp_trans.fp_trans ();
done ();
}
void
RedrawThread::wakeup_checked ()
{
tl::Clock c = tl::Clock::current ();
if ((c - m_clock).seconds () > update_interval * 0.8 * 0.001) {
m_clock = c;
wakeup ();
}
}
void
RedrawThread::wakeup ()
{
bool send_event = false;
// wakeup the main thread on the initial update ..
m_initial_wait_lock.lock ();
if (m_initial_update) {
m_initial_wait_cond.wakeAll ();
m_initial_update = false;
} else {
send_event = true;
}
m_initial_wait_lock.unlock ();
// otherwise post an event to actually draw
if (send_event) {
mp_canvas->signal_transfer_done ();
}
}
} // namespace lay