diff --git a/HEN_HOUSE/egs++/view/image_window.cpp b/HEN_HOUSE/egs++/view/image_window.cpp index b85e8e8df..57055f0e6 100644 --- a/HEN_HOUSE/egs++/view/image_window.cpp +++ b/HEN_HOUSE/egs++/view/image_window.cpp @@ -40,6 +40,7 @@ #include #include #include +#include // The below shim exists to ensure that QThread by default // runs an event loop for older Qt versions. @@ -73,9 +74,11 @@ ImageWindow::ImageWindow(QWidget *parent, const char *name) : lastResult.elapsedTime = -1.; lastRequestGeo = NULL; - saveProgress = NULL; + wasLastRequestSlow = false; regionsDisplayed = true; + isSaving = false; + vis = new EGS_GeometryVisualizer; // register types so they can be transfered accross thread @@ -94,166 +97,105 @@ ImageWindow::~ImageWindow() { stopWorker(); delete navigationTimer; delete vis; -}; - -/* no longer in Qt4. What was this supposed to do? - void polish() { - //QDialog::polish(); - QWidget::polish(); - QWidget *topl = topLevelWidget(); - //egsWarning("In polish: position: %d %d\n",pos().x(),pos().y()); - //if( !topl ) egsWarning("Null top level widget!\n"); - QWidget *parent = parentWidget(); - if( !parent ) parent = topl; - //egsWarning("parent: %s\n",parent->name()); - if( parent ) { - QPoint point = parent->mapToGlobal(QPoint(0,0)); - //egsWarning("parent: %d %d\n",point.x(),point.y()); - //QRect my_frame = frameGeometry(); - //egsWarning("my geometry: %d %d %d %d\n",my_frame.left(), - // my_frame.right(),my_frame.top(),my_frame.bottom()); - int gview_x = point.x(); - int gview_y = point.y() + parent->height(); - //egsWarning("moving to %d %d\n",gview_x,gview_y); - move(gview_x,gview_y); - //my_frame = frameGeometry(); - //egsWarning("my geometry: %d %d %d %d\n",my_frame.left(), - // my_frame.right(),my_frame.top(),my_frame.bottom()); - } - }; - */ +} void ImageWindow::render(EGS_BaseGeometry *geo, bool transform) { if (transform) { startTransformation(); } - if (navigating) { - pars.requestType = Transformation; - } - else { - pars.requestType = FullDetail; - } rerender(geo); } void ImageWindow::rerender(EGS_BaseGeometry *geo) { if (!thread) { - // Don't bother if the thread has been disabled + // Don't bother if the thread has been disabled. return; } lastRequestGeo = geo; - // Determine scaling depending on if the request is to - // draw at full detail or to be interactive. This flag _should_ be local - - if (pars.requestType == FullDetail) { - pars.nx = this->width(); - pars.ny = this->height(); - pars.nxr = 1; - pars.nyr = 1; - } - else if (pars.requestType == Transformation) { - // Dynamically select a good render mode - int nx=this->width(),ny=this->height(); - int nxr=nx,nyr=ny; - if (lastResult.elapsedTime > 0) { - // Cut out the track time because that is a fixed overhead. - // Otherwise, when you have a few hundred megabytes of tracks, - // the view decays to a single pixel and you _still_ get lag. - // The proper fix is some sort of track level-of-detail mechanism. - EGS_Float timePerPixel = (lastResult.elapsedTime - lastResult.trackTime) / (lastRequest.nx * lastRequest.ny); - EGS_Float target = 30.0; // msecs per frame - EGS_Float scale = nx*ny * timePerPixel / target; - - if (scale > 1) { - scale = 1./sqrt(scale); - int nnx = (int)(scale*nx), nny = (int)(scale*ny); - if (nnx < 1) { - nnx = 1; - } - if (nny < 1) { - nny = 1; - } - nxr = nx/nnx; - if (nxr*nnx != nx) { - nxr++; - } - nyr = ny/nny; - if (nyr*nny != ny) { - nyr++; - } - nnx = nx/nxr; - nny = ny/nyr; - if (nnx*nxr < nx) { - nnx++; - } - if (nny*nyr < ny) { - nny++; - } -#ifdef VIEW_DEBUG - egsWarning(" nx=%d ny=%d nnx=%d nny=%d nxr=%d nyr=%d\n", - nx,ny,nnx,nny,nxr,nyr); -#endif - nx = nnx; - ny = nny; - } - else { - nxr = 1; - nyr = 1; - } + if (renderState == WorkerCalculating || renderState == WorkerBackordered) { + // Abort slow full detail renders when fast response is needed + if (navigating && wasLastRequestSlow && !isSaving) { + worker->abort_location = 1; } - else { - // First-time scale values. - nxr = 4; - nyr = 4; - int nnx = nx/nxr, nny = ny/nyr; - if (nnx*nxr < nx) { - nx = nnx+1; + // Busy, will call once current task is complete + renderState = WorkerBackordered; + return; + } + // It should be the case that renderState == WorkerIdle. + + // Draw at full scale only if we are not currently in a transformation + int nx=this->width(),ny=this->height(); + int nxr=1,nyr=1; + wasLastRequestSlow = false; + if (!navigating) { + // Full detail, keep defaults. May be slow. + wasLastRequestSlow = true; + } + else if (lastResult.elapsedTime <= 0) { + // No previous measurements, so guess + nxr = 4; + nyr = 4; + } + else { + // Dynamic scaling. Ignore the fixed costs of rendering, and tune + // the pixel-dependent costs to take a specific amount of time. + EGS_Float target = 30.0; // msecs per frame + EGS_Float timePerPixel = lastResult.timePerPixel; + EGS_Float scale = nx*ny * timePerPixel / target; + if (scale > 1) { + int sc = (int)sqrt(scale); + nxr = sc; + nyr = sc; + if (nxr * nyr < scale) { + nxr++; } - else { - nx = nnx; + if (nxr * nyr < scale) { + nyr++; } - if (nny*nyr < ny) { - ny = nny+1; + // prefer divisors + if (nx % nxr != 0 && nx % (nxr+1) == 0) { + nxr++; } - else { - ny = nny; + if (ny % nyr != 0 && ny % (nyr+1) == 0) { + nyr++; } } - pars.nx = nx; - pars.ny = ny; - pars.nxr = nxr; - pars.nyr = nyr; - } - else { - // sizes subject to external control. + else { + // Fast enough that preemption isn't necessary + } } - - // TODO: need some fast-abort method for the worker, to cancel - // old jobs IFF they are of lower priority than the current one - - if (renderState == WorkerIdle) { - activeRequestType = pars.requestType; - emit requestRender(lastRequestGeo,pars); - renderState = WorkerCalculating; + // Determine # of pixels needed + int nnx = nx/nxr; + int nny = ny/nyr; + if (nnx*nxr < nx) { + nnx++; } - else if (renderState == WorkerCalculating) { - // abort only to interrupt a full-detail calculation by a transformation - // since the latter makes the former invalid. - if (activeRequestType == FullDetail && pars.requestType == Transformation) { - worker->abort_location = 1; - } - renderState = WorkerBackordered; + if (nny*nyr < ny) { + nny++; } -} + pars.nx = nnx; + pars.ny = nny; + pars.nxr = nxr; + pars.nyr = nyr; +#ifdef VIEW_DEBUG + egsWarning(" nx=%d ny=%d nnx=%d nny=%d nxr=%d nyr=%d\n", + nx,ny,nnx,nny,nxr,nyr); +#endif -void ImageWindow::loadTracks(QString name) { - emit requestLoadTracks(name); + renderState = WorkerCalculating; + emit requestRender(lastRequestGeo, pars); } - void ImageWindow::saveView(EGS_BaseGeometry *geo, int nx, int ny, QString name, QString ext) { + if (isSaving) { + // Ignore new image request, as the old hasn't completed + return; + } + + lastRequestGeo = geo; + saveName = name; saveExtension = ext; // Temporarily change parameters to render at new resolution @@ -263,12 +205,19 @@ void ImageWindow::saveView(EGS_BaseGeometry *geo, int nx, int ny, QString name, pars.nx = nx; pars.ny = ny; pars.requestType = SavedImage; - rerender(geo); + // Queue directly to maintain correct state + emit requestRender(lastRequestGeo, pars); + pars.nx = oldnx; pars.ny = oldny; pars.requestType = oldrq; - saveProgress = new QProgressDialog("Saving image", "&Cancel", 0, 2, this); - saveProgress->setMinimumDuration(500); + + isSaving = true; +} + + +void ImageWindow::loadTracks(QString name) { + emit requestLoadTracks(name); } void ImageWindow::stopWorker() { @@ -296,6 +245,7 @@ void ImageWindow::restartWorker() { connect(worker, SIGNAL(rendered(RenderResults,RenderParameters)), this, SLOT(drawResults(RenderResults,RenderParameters))); connect(worker, SIGNAL(aborted()), this, SLOT(handleAbort())); thread->start(); + renderState = WorkerIdle; } void ImageWindow::startTransformation() { @@ -320,26 +270,7 @@ void ImageWindow::resizeEvent(QResizeEvent *e) { if (e->size() != e->oldSize() && lastRequestGeo) { // treat this as a transformation, since more resizes tend to follow - startTransformation(); - render(lastRequestGeo, false); - } -}; - -void ImageWindow::paintBackground(QPainter &p) { - const RenderParameters &q = lastRequest; - const RenderResults &r = lastResult; - if (q.nxr == 1 && q.nyr == 1) { - p.drawImage(QPoint(0,0),r.img); - } - else { - p.drawImage(QRect(0,0,q.nxr * q.nx, q.nyr * q.ny),r.img); - } - - if (q.draw_axeslabels) { - p.setPen(QColor(255,255,255)); - p.drawText((int)(q.nxr*r.axeslabelsX.x-3),q.nyr*q.ny-(int)(q.nyr*r.axeslabelsX.y-3),"x"); - p.drawText((int)(q.nxr*r.axeslabelsY.x-3),q.nyr*q.ny-(int)(q.nyr*r.axeslabelsY.y-3),"y"); - p.drawText((int)(q.nxr*r.axeslabelsZ.x-3),q.nyr*q.ny-(int)(q.nyr*r.axeslabelsZ.y-3),"z"); + render(lastRequestGeo, true); } } @@ -360,7 +291,7 @@ void ImageWindow::paintEvent(QPaintEvent *) { rerenderRequested = false; if (wasRerenderRequested) { QPainter p(this); - paintBackground(p); + p.drawImage(QPoint(0,0),r.img); p.end(); } @@ -408,7 +339,7 @@ void ImageWindow::paintEvent(QPaintEvent *) { if (regionsDisplayed) { regionsDisplayed=false; // repaint just the eclipsed region (painter has clip) - paintBackground(p); + p.drawImage(QPoint(0,0),r.img); } p.end(); return; @@ -515,7 +446,7 @@ void ImageWindow::mouseMoveEvent(QMouseEvent *event) { // picking this->update(); } -}; +} void ImageWindow::wheelEvent(QWheelEvent *event) { #ifdef VIEW_DEBUG @@ -524,7 +455,7 @@ void ImageWindow::wheelEvent(QWheelEvent *event) { #endif startTransformation(); emit cameraZooming(event->delta()/20); -}; +} void ImageWindow::keyPressEvent(QKeyEvent *event) { #ifdef VIEW_DEBUG @@ -551,12 +482,19 @@ void ImageWindow::keyPressEvent(QKeyEvent *event) { else { (event->ignore()); } -}; +} void ImageWindow::drawResults(RenderResults r, RenderParameters q) { + if (q.requestType == SavedImage) { + // Short circuit images (they don't show up) + r.img.save(saveName, saveExtension.toLatin1().constData()); + isSaving = false; + emit saveComplete(); + return; + } + lastResult = r; lastRequest = q; - // update the render thread status and queue next image if necessary switch (renderState) { case WorkerBackordered: @@ -571,31 +509,17 @@ void ImageWindow::drawResults(RenderResults r, RenderParameters q) { break; } - if (lastRequest.requestType == SavedImage) { - if (saveProgress) { - if (!saveProgress->wasCanceled()) { - lastResult.img.save(saveName, saveExtension.toLatin1().constData()); - } - delete saveProgress; - saveProgress = NULL; - } - } - else { - if (saveProgress) { - saveProgress->setValue(1); - } + rerenderRequested = true; - rerenderRequested = true; - if (!this->isVisible()) { - this->show(); - } + if (!this->isVisible()) { + this->show(); + } - // Synchronize the local visualizer precisely with - // what is currently on screen. - applyParameters(vis, lastRequest); + // Synchronize the local visualizer precisely with + // what is currently on screen. + applyParameters(vis, lastRequest); - repaint(); - } + repaint(); } void ImageWindow::handleAbort() { @@ -607,5 +531,8 @@ void ImageWindow::handleAbort() { renderState = WorkerIdle; rerender(lastRequestGeo); } + else if (renderState == WorkerCalculating) { + renderState = WorkerIdle; + } } } diff --git a/HEN_HOUSE/egs++/view/image_window.h b/HEN_HOUSE/egs++/view/image_window.h index 185064b17..fd2acafc3 100644 --- a/HEN_HOUSE/egs++/view/image_window.h +++ b/HEN_HOUSE/egs++/view/image_window.h @@ -100,6 +100,7 @@ protected slots: void cameraHomeDefining(); void putCameraOnAxis(char axis); void leftMouseClick(int x, int y); + void saveComplete(); // for render thread void requestRender(EGS_BaseGeometry *,RenderParameters); @@ -127,12 +128,12 @@ protected slots: RenderParameters lastRequest; enum {WorkerIdle, WorkerCalculating, WorkerBackordered} renderState; EGS_BaseGeometry *lastRequestGeo; - RenderRequestType activeRequestType; + bool wasLastRequestSlow; // Image saving QString saveName; QString saveExtension; - QProgressDialog *saveProgress; + bool isSaving; }; #endif diff --git a/HEN_HOUSE/egs++/view/renderworker.cpp b/HEN_HOUSE/egs++/view/renderworker.cpp index 766a2b257..edab0112c 100644 --- a/HEN_HOUSE/egs++/view/renderworker.cpp +++ b/HEN_HOUSE/egs++/view/renderworker.cpp @@ -27,15 +27,18 @@ */ #include "renderworker.h" + #include "egs_visualizer.h" -#include "qdatetime.h" +#include +#include +#include RenderWorker::RenderWorker() { vis = new EGS_GeometryVisualizer; image = NULL; - nx_last = -1; - ny_last = -1; + buffer = NULL; + last_bufsize = -1; abort_location = 0; } RenderWorker::~RenderWorker() { @@ -43,6 +46,9 @@ RenderWorker::~RenderWorker() { if (image) { delete[] image; } + if (buffer) { + delete[] buffer; + } } void RenderWorker::loadTracks(QString fileName) { @@ -115,16 +121,31 @@ void RenderWorker::drawAxes(const RenderParameters &p) { EGS_Float deltax, deltay; // loop over axes for (int k=1; k<=3; k++) { + // note: float->int casts overflow at high zoom levels. int i1 = (int) axes[k].x; int j1 = (int) axes[k].y; - int n = abs(i1-i0); - if (abs(j1-j0)>n) { - n = abs(j1-j0); + int di = i1 - i0; + int dj = j1 - j0; + // just one axis pixel; don't bother looping + if (j1 == j0 && i1 == i0) { + if (i1>=0 && i1=0 && j10) { - deltax = (i1-i0)/(float)n; - deltay = (j1-j0)/(float)n; + else { + int n; + if (abs(di) < abs(dj)) { + int sign = j1 > j0 ? 1 : -1; + deltax = sign*(float)(di) / (float)(dj); + deltay = sign; + n = abs(dj) > ny ? ny : abs(dj); + } + else { + int sign = i1 > i0 ? 1 : -1; + deltax = sign; + deltay = sign*(float)(dj) / (float)(di); + n = abs(di) > nx ? nx : abs(di); + } for (int t=0; t<=n; t++) { i1 = (int)(i0+t*deltax); j1 = (int)(j0+t*deltay); @@ -142,10 +163,6 @@ void RenderWorker::drawAxes(const RenderParameters &p) { } } } - // just one axis pixel - else if (i1>=0 && i1=0 && j1 last_bufsize || last_bufsize > 3*new_bufsize) { + if (image) { + delete[] image; + } + if (buffer) { + delete[] buffer; + } + image = new EGS_Vector[new_bufsize]; + buffer = new QRgb[new_bufsize]; + last_bufsize = new_bufsize; } // modifies image and sets axeslabels @@ -185,7 +223,6 @@ void RenderWorker::render(EGS_BaseGeometry *g, struct RenderParameters p) { drawAxes(p); } - QTime pretracktime = QTime::currentTime(); // render tracks if (p.draw_tracks) { vis->setParticleVisibility(1,p.show_photons); @@ -193,38 +230,80 @@ void RenderWorker::render(EGS_BaseGeometry *g, struct RenderParameters p) { vis->setParticleVisibility(3,p.show_positrons); vis->setParticleVisibility(4,p.show_other); if (!vis->renderTracks(g,p.nx,p.ny,image,&abort_location)) { - emit aborted(); - return; + // Undo track drawing and rezero image + memset(image, 0, sizeof(EGS_Vector)); + return r; } } - QTime posttracktime = QTime::currentTime(); // render main geometry + QTime time_r1 = QTime::currentTime(); if (!vis->renderImage(g,p.nx,p.ny,image,&abort_location)) { - emit aborted(); - return; + return r; } + QTime time_r2 = QTime::currentTime(); - // transfer to image - QImage img(p.nx, p.ny, QImage::Format_ARGB32); - for (int j=0; jpushButton5->setEnabled(false); gview->saveView(g,nx,ny,fname,format); } +void GeometryViewControl::reenableSave() { + this->pushButton5->setEnabled(true); +} void GeometryViewControl::showHideOptions() { #ifdef VIEW_DEBUG diff --git a/HEN_HOUSE/egs++/view/viewcontrol.h b/HEN_HOUSE/egs++/view/viewcontrol.h index 2abfd8a42..29906fe8a 100644 --- a/HEN_HOUSE/egs++/view/viewcontrol.h +++ b/HEN_HOUSE/egs++/view/viewcontrol.h @@ -106,6 +106,7 @@ public slots: virtual void updateColorLabel(int med); virtual void changeColor(); virtual void saveImage(); + virtual void reenableSave(); virtual void showHideOptions(); virtual void setClippingPlanes(); virtual void showPhotonsCheckbox_toggled(bool toggle);