diff options
| -rw-r--r-- | README.md | 9 | ||||
| -rw-r--r-- | gui/base.qrc | 2 | ||||
| -rw-r--r-- | gui/basewindow.cc | 50 | ||||
| -rw-r--r-- | gui/basewindow.h | 6 | ||||
| -rw-r--r-- | gui/fpgaviewwidget.cc | 38 | ||||
| -rw-r--r-- | gui/fpgaviewwidget.h | 8 | ||||
| -rw-r--r-- | gui/resources/camera.png | bin | 0 -> 665 bytes | |||
| -rw-r--r-- | gui/resources/film.png | bin | 0 -> 653 bytes | 
8 files changed, 111 insertions, 2 deletions
| @@ -161,6 +161,15 @@ Notes for developers  - To automatically format all source code, run `make clangformat`.  - See the wiki for additional documentation on the architecture API. +Recording a movie +----------------- + +- To save a movie recording of place-and-route click recording icon in toolbar and select empty directory +  where recording files will be stored and select frames to skip. +- Manualy start all PnR operations you wish +- Click on recording icon again to stop recording +- Go to directory containing files and exeecute `ffmpeg -f image2 -r 1 -i movie_%05d.png -c:v libx264 nextpnr.mp4` +  Testing  ------- diff --git a/gui/base.qrc b/gui/base.qrc index 63612bf4..0671fa9e 100644 --- a/gui/base.qrc +++ b/gui/base.qrc @@ -28,5 +28,7 @@          <file>resources/wire.png</file>          <file>resources/pip.png</file>          <file>resources/group.png</file> +        <file>resources/camera.png</file> +        <file>resources/film.png</file>      </qresource>  </RCC> diff --git a/gui/basewindow.cc b/gui/basewindow.cc index c3dbc131..0bfc59a3 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -23,6 +23,7 @@  #include <QFileDialog>
  #include <QGridLayout>
  #include <QIcon>
 +#include <QImageWriter>
  #include <QInputDialog>
  #include <QSplitter>
  #include <fstream>
 @@ -252,6 +253,18 @@ void BaseMainWindow::createMenusAndBars()      actionDisplayGroups->setChecked(true);
      connect(actionDisplayGroups, &QAction::triggered, this, &BaseMainWindow::enableDisableDecals);
 +    actionScreenshot = new QAction("Screenshot", this);
 +    actionScreenshot->setIcon(QIcon(":/icons/resources/camera.png"));
 +    actionScreenshot->setStatusTip("Taking a screenshot");
 +    connect(actionScreenshot, &QAction::triggered, this, &BaseMainWindow::screenshot);
 +
 +    actionMovie = new QAction("Recording", this);
 +    actionMovie->setIcon(QIcon(":/icons/resources/film.png"));
 +    actionMovie->setStatusTip("Saving a movie");
 +    actionMovie->setCheckable(true);
 +    actionMovie->setChecked(false);
 +    connect(actionMovie, &QAction::triggered, this, &BaseMainWindow::saveMovie);
 +
      // set initial state
      fpgaView->enableDisableDecals(actionDisplayBel->isChecked(), actionDisplayWire->isChecked(),
                                    actionDisplayPip->isChecked(), actionDisplayGroups->isChecked());
 @@ -317,6 +330,9 @@ void BaseMainWindow::createMenusAndBars()      deviceViewToolBar->addAction(actionDisplayWire);
      deviceViewToolBar->addAction(actionDisplayPip);
      deviceViewToolBar->addAction(actionDisplayGroups);
 +    deviceViewToolBar->addSeparator();
 +    deviceViewToolBar->addAction(actionScreenshot);
 +    deviceViewToolBar->addAction(actionMovie);
      // Add status bar with progress bar
      statusBar = new QStatusBar();
 @@ -362,6 +378,40 @@ void BaseMainWindow::save_json()      }
  }
 +void BaseMainWindow::screenshot()
 +{
 +    QString fileName = QFileDialog::getSaveFileName(this, QString("Save screenshot"), QString(), QString("*.png"));
 +    if (!fileName.isEmpty()) {
 +        QImage image = fpgaView->grabFramebuffer();
 +        if (!fileName.endsWith(".png"))
 +            fileName += ".png";
 +        QImageWriter imageWriter(fileName, "png");
 +        if (imageWriter.write(image))
 +            log("Saving screenshot successful.\n");
 +        else
 +            log("Saving screenshot failed.\n");
 +    }
 +}
 +
 +void BaseMainWindow::saveMovie()
 +{
 +    if (actionMovie->isChecked()) {
 +        QString dir = QFileDialog::getExistingDirectory(this, tr("Select Movie Directory"), QDir::currentPath(),
 +                                                        QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
 +        if (!dir.isEmpty()) {
 +            bool ok;
 +            int frames = QInputDialog::getInt(this, "Skip frames", tr("Frames to skip (1 frame = 50ms):"), 5, 0, 1000,
 +                                              1, &ok);
 +            if (ok)
 +                fpgaView->movieStart(dir, frames);
 +            else
 +                actionMovie->setChecked(false);
 +        } else
 +            actionMovie->setChecked(false);
 +    } else {
 +        fpgaView->movieStop();
 +    }
 +}
  void BaseMainWindow::pack_finished(bool status)
  {
      disableActions();
 diff --git a/gui/basewindow.h b/gui/basewindow.h index 7562307e..fe9dfdf2 100644 --- a/gui/basewindow.h +++ b/gui/basewindow.h @@ -83,6 +83,9 @@ class BaseMainWindow : public QMainWindow      void taskStarted();
      void taskPaused();
 +    void screenshot();
 +    void saveMovie();
 +
    Q_SIGNALS:
      void contextChanged(Context *ctx);
      void updateTreeView();
 @@ -128,6 +131,9 @@ class BaseMainWindow : public QMainWindow      QAction *actionDisplayWire;
      QAction *actionDisplayPip;
      QAction *actionDisplayGroups;
 +
 +    QAction *actionScreenshot;
 +    QAction *actionMovie;
  };
  NEXTPNR_NAMESPACE_END
 diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 2e1bbf63..8730c9d9 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -22,6 +22,9 @@  #include <QApplication>  #include <QCoreApplication> +#include <QDir> +#include <QFileInfo> +#include <QImageWriter>  #include <QMouseEvent>  #include <QWidget> @@ -35,7 +38,7 @@  NEXTPNR_NAMESPACE_BEGIN  FPGAViewWidget::FPGAViewWidget(QWidget *parent) -        : QOpenGLWidget(parent), ctx_(nullptr), paintTimer_(this), lineShader_(this), zoom_(10.0f), +        : QOpenGLWidget(parent), movieSaving(false), ctx_(nullptr), paintTimer_(this), lineShader_(this), zoom_(10.0f),            rendererArgs_(new FPGAViewWidget::RendererArgs), rendererData_(new FPGAViewWidget::RendererData)  {      colors_.background = QColor("#000000"); @@ -322,6 +325,23 @@ void FPGAViewWidget::paintGL()      lineShader_.draw(GraphicElement::STYLE_SELECTED, colors_.selected, thick11Px, matrix);      lineShader_.draw(GraphicElement::STYLE_HOVER, colors_.hovered, thick2Px, matrix); +    if (movieSaving) { +        if (movieCounter == currentFrameSkip) { +            QMutexLocker lock(&rendererArgsLock_); +            movieCounter = 0; +            currentMovieFrame++; + +            QImage image = grabFramebuffer(); +            QString number = QString("movie_%1.png").arg(currentMovieFrame, 5, 10, QChar('0')); + +            QFileInfo fileName = QFileInfo(QDir(movieDir), number); +            QImageWriter imageWriter(fileName.absoluteFilePath(), "png"); +            imageWriter.write(image); +        } else { +            movieCounter++; +        } +    } +      // Render ImGui      QtImGui::newFrame();      QMutexLocker lock(&rendererArgsLock_); @@ -579,6 +599,22 @@ void FPGAViewWidget::renderLines(void)      }  } +void FPGAViewWidget::movieStart(QString dir, long frameSkip) +{ +    QMutexLocker locker(&rendererArgsLock_); +    movieDir = dir; +    currentMovieFrame = 0; +    movieCounter = 0; +    currentFrameSkip = frameSkip; +    movieSaving = true; +} + +void FPGAViewWidget::movieStop() +{ +    QMutexLocker locker(&rendererArgsLock_); +    movieSaving = false; +} +  void FPGAViewWidget::onSelectedArchItem(std::vector<DecalXY> decals, bool keep)  {      { diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index 735590ba..7f99408e 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -120,13 +120,19 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions      void zoomSelected();      void zoomOutbound();      void enableDisableDecals(bool bels, bool wires, bool pips, bool groups); - +    void movieStart(QString dir, long frameSkip); +    void movieStop();    Q_SIGNALS:      void clickedBel(BelId bel, bool add);      void clickedWire(WireId wire, bool add);      void clickedPip(PipId pip, bool add);    private: +    QString movieDir; +    long currentMovieFrame; +    long currentFrameSkip; +    long movieCounter; +    bool movieSaving;      const float zoomNear_ = 0.05f; // do not zoom closer than this      float zoomFar_ = 10.0f;        // do not zoom further than this      const float zoomLvl1_ = 1.0f; diff --git a/gui/resources/camera.png b/gui/resources/camera.pngBinary files differ new file mode 100644 index 00000000..8536d1a7 --- /dev/null +++ b/gui/resources/camera.png diff --git a/gui/resources/film.png b/gui/resources/film.pngBinary files differ new file mode 100644 index 00000000..b0ce7bb1 --- /dev/null +++ b/gui/resources/film.png | 
