mirror of https://github.com/KLayout/klayout.git
434 lines
13 KiB
C++
434 lines
13 KiB
C++
|
|
/*
|
|
|
|
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
|